diff --git a/.github/workflows/automate_issue_labels.yml b/.github/workflows/automate_issue_labels.yml index 8c36daa012ca..180aeef04c82 100644 --- a/.github/workflows/automate_issue_labels.yml +++ b/.github/workflows/automate_issue_labels.yml @@ -53,13 +53,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} ISSUE: ${{ github.event.issue.number }} - - name: Check for APIDocs related issue - if: ${{ contains(github.event.issue.body, '-> API Docs') }} - run: gh issue transfer $ISSUE "ballerina-platform/ballerina-dev-tools" - env: - GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} - ISSUE: ${{ github.event.issue.number }} - - name: Check for Debugger related issue if: ${{ contains(github.event.issue.body, '-> Debugger') }} run: | diff --git a/.github/workflows/daily_build.yml b/.github/workflows/daily_build.yml index 15f159951f2d..f861e10a339f 100644 --- a/.github/workflows/daily_build.yml +++ b/.github/workflows/daily_build.yml @@ -9,7 +9,7 @@ jobs: ubuntu_build: name: Build with tests on Ubuntu runs-on: ubuntu-latest - timeout-minutes: 75 + timeout-minutes: 120 steps: - name: Checkout Repository diff --git a/.github/workflows/pull_request_windows_build.yml b/.github/workflows/pull_request_windows_build.yml index fd5212b0fb89..8eca0c156228 100644 --- a/.github/workflows/pull_request_windows_build.yml +++ b/.github/workflows/pull_request_windows_build.yml @@ -51,9 +51,18 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: ${{ runner.os }}-gradle + - name: Setup GraalVM + uses: graalvm/setup-graalvm@v1 + with: + java-version: '17.0.7' + distribution: 'graalvm' + components: 'native-image' + github-token: ${{ secrets.GITHUB_TOKEN }} + set-java-home: 'false' + - name: Build with Gradle env: packageUser: ${{ github.actor }} packagePAT: ${{ secrets.GITHUB_TOKEN }} - run: ./gradlew.bat build --continue -x :ballerina-lang:test -x :jballerina-integration-test:test -x :ballerina-shell:shell-cli:test -x :ballerina-cli:test -x javadoc --stacktrace -scan --console=plain --no-daemon --no-parallel + run: ./gradlew.bat build --continue -x :ballerina-lang:test -x :jballerina-integration-test:test -x javadoc --stacktrace -scan --console=plain --no-daemon --no-parallel diff --git a/.github/workflows/push_master.yml b/.github/workflows/push_master.yml index 9890f7514d96..962eef94076a 100644 --- a/.github/workflows/push_master.yml +++ b/.github/workflows/push_master.yml @@ -54,7 +54,7 @@ jobs: windows_build: name: Build with some tests on Windows runs-on: windows-latest - timeout-minutes: 120 + timeout-minutes: 150 concurrency: group: ${{ github.head_ref }}-windows cancel-in-progress: true @@ -86,8 +86,17 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: ${{ runner.os }}-gradle + - name: Setup GraalVM + uses: graalvm/setup-graalvm@v1 + with: + java-version: '17.0.7' + distribution: 'graalvm' + components: 'native-image' + github-token: ${{ secrets.GITHUB_TOKEN }} + set-java-home: 'false' + - name: Build with Gradle - run: ./gradlew.bat build --continue -x :ballerina-lang:test -x :jballerina-integration-test:test -x :ballerina-shell:shell-cli:test -x :jballerina-debugger-integration-test:test -x javadoc --stacktrace -scan --console=plain --no-daemon --no-parallel + run: ./gradlew.bat build --continue -x :ballerina-lang:test -x :jballerina-integration-test:test -x :jballerina-debugger-integration-test:test -x javadoc --stacktrace -scan --console=plain --no-daemon --no-parallel sonarcloud_scan: diff --git a/ballerina-shell/README.md b/ballerina-shell/README.md index 67cce916984d..69470069459f 100644 --- a/ballerina-shell/README.md +++ b/ballerina-shell/README.md @@ -16,12 +16,12 @@ The Ballerina-shell tool is an interactive tool for learning the Ballerina progr The project is implemented in three base modules. - **shell-rt** - Module including runtime dependencies for ballerina programs generated. You may find the source code for this - module [here](shell-rt). + module [here](modules/shell-rt). - **shell-core** - Module including all the base evaluation classes. This has all the base components to evaluate and run a string. All other components are built on top of this module. You may find the source code for this - module [here](shell-core). + module [here](modules/shell-core). - **shell-cli** - A command-line interface built on top of shell. Includes multi-line inputs, color-coded outputs, - keyword-based auto-completion, etc... You may find the source code for this module [here](shell-cli). + keyword-based auto-completion, etc... You may find the source code for this module [here](modules/shell-cli). ## Known Issues @@ -103,7 +103,7 @@ The project is implemented in three base modules. ## Implementation -For implementation details please refer [this](shell-core/README.md). +For implementation details please refer [this](modules/shell-core/README.md). ## Building diff --git a/ballerina-shell/modules/shell-cli/src/test/java/io/ballerina/shell/cli/test/base/TestIntegrator.java b/ballerina-shell/modules/shell-cli/src/test/java/io/ballerina/shell/cli/test/base/TestIntegrator.java index f1821a8c10b9..90932b373362 100644 --- a/ballerina-shell/modules/shell-cli/src/test/java/io/ballerina/shell/cli/test/base/TestIntegrator.java +++ b/ballerina-shell/modules/shell-cli/src/test/java/io/ballerina/shell/cli/test/base/TestIntegrator.java @@ -28,7 +28,6 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; @@ -62,8 +61,8 @@ public TestIntegrator(InputStream inputStream, OutputStream outputStream, ByteAr @Override public void run() { try { - PrintStream testPrint = new PrintStream(outputStream, true, Charset.defaultCharset()); - InputStreamReader inStreamReader = new InputStreamReader(inputStream, Charset.defaultCharset()); + PrintStream testPrint = new PrintStream(outputStream, true, StandardCharsets.UTF_8); + InputStreamReader inStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); BufferedReader testReader = new BufferedReader(inStreamReader); // The response here is not testable because it can change. @@ -117,9 +116,8 @@ private String readResponse(BufferedReader stream) throws IOException { * Send the data given to the specific stream. */ private void sendRequest(PrintStream stream, String string) throws InterruptedException { - stream.append(string); - stream.println(System.lineSeparator()); - stream.flush(); + // This is a workaround since with double `System.lineSeparator()` the tests fail on windows. + stream.append(string).append("\n\n").flush(); } /** diff --git a/ballerina-shell/modules/shell-cli/src/test/java/io/ballerina/shell/cli/test/integration/AbstractIntegrationTest.java b/ballerina-shell/modules/shell-cli/src/test/java/io/ballerina/shell/cli/test/integration/AbstractIntegrationTest.java index a9ba48fc3522..4cb43ad4bcf3 100644 --- a/ballerina-shell/modules/shell-cli/src/test/java/io/ballerina/shell/cli/test/integration/AbstractIntegrationTest.java +++ b/ballerina-shell/modules/shell-cli/src/test/java/io/ballerina/shell/cli/test/integration/AbstractIntegrationTest.java @@ -30,7 +30,7 @@ import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.PrintStream; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.List; /** @@ -54,7 +54,7 @@ protected void test(String fileName) throws Exception { PrintStream origOut = System.out; try { ByteArrayOutputStream stdOut = new ByteArrayOutputStream(); - PrintStream interceptedOutStream = new PrintStream(stdOut, true, Charset.defaultCharset()); + PrintStream interceptedOutStream = new PrintStream(stdOut, true, StandardCharsets.UTF_8); System.setOut(interceptedOutStream); TestIntegrator testIntegrator = new TestIntegrator(testIn, testOut, stdOut, testCases); diff --git a/ballerina-shell/modules/shell-core/src/main/java/io/ballerina/shell/parser/trials/ExpressionTrial.java b/ballerina-shell/modules/shell-core/src/main/java/io/ballerina/shell/parser/trials/ExpressionTrial.java index 4f19a15fa9e9..2461be5382f7 100644 --- a/ballerina-shell/modules/shell-core/src/main/java/io/ballerina/shell/parser/trials/ExpressionTrial.java +++ b/ballerina-shell/modules/shell-core/src/main/java/io/ballerina/shell/parser/trials/ExpressionTrial.java @@ -22,6 +22,7 @@ import io.ballerina.compiler.syntax.tree.ExpressionNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeParser; +import io.ballerina.shell.parser.ParserConstants; import io.ballerina.shell.parser.TrialTreeParser; import java.util.ArrayList; @@ -57,7 +58,16 @@ public Collection parse(String source) throws ParserTrialFailedException { if (expressionNode.hasDiagnostics()) { throw new ParserTrialFailedException("Error occurred during extracting expression from the statement"); } + validateExpression(expressionNode.toSourceCode()); nodes.add(expressionNode); return nodes; } + + private void validateExpression(String expression) { + String functionName = expression.replaceAll("\\s*\\(.*", ""); + if (ParserConstants.isFunctionNameRestricted(functionName)) { + String message = String.format("Function name '%s' not allowed in Ballerina Shell.%n", functionName); + throw new InvalidMethodException(message); + } + } } diff --git a/build.gradle b/build.gradle index 9f0787e174ea..b08307f70d26 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,10 @@ plugins { apply from: "$rootDir/gradle/repositories.gradle" allprojects { + tasks.withType(JavaCompile).configureEach { + options.fork = true + } + group = project.group version = project.version @@ -144,4 +148,3 @@ sonarqube { } copyBallerinaClassFiles.dependsOn copyExecFilesAndJavaClassFiles -createCodeCoverageReport.dependsOn copyBallerinaClassFiles diff --git a/bvm/ballerina-profiler/build.gradle b/bvm/ballerina-profiler/build.gradle index dd0b44fe5d35..89e3796fae12 100644 --- a/bvm/ballerina-profiler/build.gradle +++ b/bvm/ballerina-profiler/build.gradle @@ -18,6 +18,10 @@ apply from: "$rootDir/gradle/javaProject.gradle" +configurations { + runtimeClasspath.transitive = false +} + dependencies { implementation "org.ow2.asm:asm:${project.ow2AsmVersion}" implementation "org.ow2.asm:asm-commons:${project.ow2AsmCommonsVersion}" @@ -30,20 +34,8 @@ dependencies { version = 1.0 jar { - dependsOn(':identifier-util:jar') - dependsOn(':ballerina-runtime:jar') - from(sourceSets.main.output) - from(sourceSets.main.java) { - include "**/*.java" - } - - from(configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }) { - exclude "META-INF/*.SF" - exclude "META-INF/*.DSA" - exclude "META-INF/*.RSA" - } duplicatesStrategy = DuplicatesStrategy.EXCLUDE - + from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } manifest { attributes 'Main-Class': 'io.ballerina.runtime.profiler.Main' } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/Repository.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/Repository.java index 5ddbfe65ee31..af4f803c91c6 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/Repository.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/Repository.java @@ -29,11 +29,17 @@ public interface Repository { * Get the list of runtime artifacts. * @return List of artifacts that contains information about the active services and listeners. */ - public List getArtifacts(); + List getArtifacts(); /** * Get the current Ballerina node. * @return Ballerina node. */ - public Node getNode(); + Node getNode(); + + /** + * Get whether remote management is enabled. + * @return True if remote management is enabled, false otherwise. + */ + boolean isRemoteManagementEnabled(); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/AnnotationUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/AnnotationUtils.java index 2615a721150d..d80da4349667 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/AnnotationUtils.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/AnnotationUtils.java @@ -23,6 +23,7 @@ import io.ballerina.runtime.api.types.ResourceMethodType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.internal.scheduling.Strand; import io.ballerina.runtime.internal.types.BAnnotatableType; @@ -58,7 +59,13 @@ public static void processAnnotations(MapValue globalAnnotMap, Type bType) { type.setAnnotations((MapValue) globalAnnotMap.get(annotationKey)); } - if (type.getTag() != TypeTags.OBJECT_TYPE_TAG && type.getTag() != TypeTags.SERVICE_TAG) { + if (type.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { + Type impliedType = TypeUtils.getImpliedType(type); + if (isNonObjectType(impliedType.getTag())) { + return; + } + type = (BAnnotatableType) impliedType; + } else if (isNonObjectType(type.getTag())) { return; } BObjectType objectType = (BObjectType) type; @@ -75,6 +82,10 @@ public static void processAnnotations(MapValue globalAnnotMap, Type bType) { } } + private static boolean isNonObjectType(int impliedTypeTag) { + return impliedTypeTag != TypeTags.OBJECT_TYPE_TAG && impliedTypeTag != TypeTags.SERVICE_TAG; + } + private static void setMethodAnnotations(MapValue globalAnnotMap, BString annotationKey, BMethodType resourceMethod) { if (globalAnnotMap.containsKey(annotationKey)) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/RepositoryImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/RepositoryImpl.java index 85b73c8b793a..002c04780501 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/RepositoryImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/RepositoryImpl.java @@ -43,7 +43,7 @@ public class RepositoryImpl implements Repository { private static final String nodeId = generateNodeId(); private static String balHome; private static String balVersion; - private static boolean isRemoteEnabled = false; + private static boolean isRemoteManagementEnabled = false; @Override public List getArtifacts() { @@ -72,6 +72,11 @@ public Node getNode() { System.getProperty("os.version")); } + @Override + public boolean isRemoteManagementEnabled() { + return isRemoteManagementEnabled; + } + private Artifact createArtifact(ObjectValue service, ObjectValue listener) { ArtifactImpl artifact = new ArtifactImpl(service.toString(), Artifact.ArtifactType.SERVICE); List listeners = (List) artifact.getDetail("listeners"); @@ -87,7 +92,7 @@ private Artifact createArtifact(ObjectValue service, ObjectValue listener) { } public static void addServiceListener(BObject listener, BObject service, Object attachPoint) { - if (!isRemoteEnabled) { + if (!isRemoteManagementEnabled) { return; } BServiceType serviceType = (BServiceType) service.getType(); @@ -96,10 +101,11 @@ public static void addServiceListener(BObject listener, BObject service, Object listenerServiceMap.put((ObjectValue) listener, (ObjectValue) service); } - public static void addBallerinaRuntimeInformation(String balHome, String balVersion, boolean isRemoteEnabled) { + public static void addBallerinaRuntimeInformation(String balHome, String balVersion, + boolean isRemoteManagementEnabled) { RepositoryImpl.balHome = balHome; RepositoryImpl.balVersion = balVersion; - RepositoryImpl.isRemoteEnabled = isRemoteEnabled; + RepositoryImpl.isRemoteManagementEnabled = isRemoteManagementEnabled; } private static String generateNodeId() { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValue.java index 4c8c0a610c37..af4febe73476 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValue.java @@ -33,6 +33,10 @@ public interface ArrayValue extends RefValue, BArray, CollectionValue { @Override Object shift(long index); + Object pop(long index); + + Object remove(long index); + @Override void setLength(long length); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java index e57cf695a662..83263e70c56a 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java @@ -705,6 +705,16 @@ public void append(Object value) { add(this.size, value); } + @Override + public Object pop(long index) { + return shift(index); + } + + @Override + public Object remove(long index) { + return shift(index); + } + @Override public Object shift(long index) { handleImmutableArrayValue(); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java index 2bdc52b627bb..baf8b1029ff4 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java @@ -423,8 +423,20 @@ public void append(Object value) { @Override public Object shift(long index) { + return shift(index, "shift"); + } + + public Object pop(long index) { + return shift(index, "pop"); + } + + public Object remove(long index) { + return shift(index, "remove"); + } + + public Object shift(long index, String operation) { handleImmutableArrayValue(); - validateTupleSizeAndInherentType(); + validateTupleSizeAndInherentType((int) index, operation); Object val = get(index); shiftArray((int) index); return val; @@ -827,15 +839,15 @@ private void resetSize(int index) { } } - private void validateTupleSizeAndInherentType() { + private void validateTupleSizeAndInherentType(int index, String operation) { List tupleTypesList = this.tupleType.getTupleTypes(); int numOfMandatoryTypes = tupleTypesList.size(); if (numOfMandatoryTypes >= this.getLength()) { throw ErrorHelper.getRuntimeException(getModulePrefixedReason(ARRAY_LANG_LIB, - OPERATION_NOT_SUPPORTED_IDENTIFIER), ErrorCodes.INVALID_TUPLE_MEMBER_SIZE, "shift"); + OPERATION_NOT_SUPPORTED_IDENTIFIER), ErrorCodes.INVALID_TUPLE_MEMBER_SIZE, operation); } // Check if value belonging to i th type can be assigned to i-1 th type (Checking done by value, not type) - for (int i = 1; i <= numOfMandatoryTypes; i++) { + for (int i = index + 1; i <= numOfMandatoryTypes; i++) { if (!TypeChecker.checkIsType(this.getRefValue(i), tupleTypesList.get(i - 1))) { throw ErrorHelper.getRuntimeException(getModulePrefixedReason(ARRAY_LANG_LIB, INHERENT_TYPE_VIOLATION_ERROR_IDENTIFIER), ErrorCodes.INCOMPATIBLE_TYPE, diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/transactions/TransactionConstants.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/transactions/TransactionConstants.java index 345b8f82cac1..f7ee5d9e9d8a 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/transactions/TransactionConstants.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/transactions/TransactionConstants.java @@ -70,4 +70,6 @@ public class TransactionConstants { public static final String ANN_NAME_TRX_PARTICIPANT_CONFIG = "Participant"; public static final String TIMESTAMP_OBJECT_VALUE_FIELD = "timeValue"; + public static final int DEFAULT_TRX_AUTO_COMMIT_TIMEOUT = 120; + public static final int DEFAULT_TRX_CLEANUP_TIMEOUT = 600; } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/transactions/TransactionResourceManager.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/transactions/TransactionResourceManager.java index b12e8a342b95..da7c241b5116 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/transactions/TransactionResourceManager.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/transactions/TransactionResourceManager.java @@ -59,6 +59,8 @@ import javax.transaction.xa.Xid; import static io.ballerina.runtime.api.constants.RuntimeConstants.BALLERINA_BUILTIN_PKG_PREFIX; +import static io.ballerina.runtime.transactions.TransactionConstants.DEFAULT_TRX_AUTO_COMMIT_TIMEOUT; +import static io.ballerina.runtime.transactions.TransactionConstants.DEFAULT_TRX_CLEANUP_TIMEOUT; import static io.ballerina.runtime.transactions.TransactionConstants.TRANSACTION_PACKAGE_ID; import static io.ballerina.runtime.transactions.TransactionConstants.TRANSACTION_PACKAGE_NAME; import static io.ballerina.runtime.transactions.TransactionConstants.TRANSACTION_PACKAGE_VERSION; @@ -85,6 +87,8 @@ public class TransactionResourceManager { private static final String ATOMIKOS_LOG_BASE_PROPERTY = "com.atomikos.icatch.log_base_dir"; private static final String ATOMIKOS_LOG_NAME_PROPERTY = "com.atomikos.icatch.log_base_name"; private static final String ATOMIKOS_REGISTERED_PROPERTY = "com.atomikos.icatch.registered"; + public static final String TRANSACTION_AUTO_COMMIT_TIMEOUT_KEY = "transactionAutoCommitTimeout"; + public static final String TRANSACTION_CLEANUP_TIMEOUT_KEY = "transactionCleanupTimeout"; private static final Logger log = LoggerFactory.getLogger(TransactionResourceManager.class); private Map> resourceRegistry; @@ -187,6 +191,56 @@ private String getTransactionLogDirectory() { } } + /** + * This method gets the user specified config for the transaction auto commit timeout. Default is 120. + * + * @return int transaction auto commit timeout value + */ + public static int getTransactionAutoCommitTimeout() { + VariableKey transactionAutoCommitTimeoutKey = new VariableKey(TRANSACTION_PACKAGE_ID, + TRANSACTION_AUTO_COMMIT_TIMEOUT_KEY, PredefinedTypes.TYPE_INT, false); + if (!ConfigMap.containsKey(transactionAutoCommitTimeoutKey)) { + return DEFAULT_TRX_AUTO_COMMIT_TIMEOUT; + } else { + Object configValue = ConfigMap.get(transactionAutoCommitTimeoutKey); + if (configValue == null) { + return DEFAULT_TRX_AUTO_COMMIT_TIMEOUT; + } + return parseTimeoutValue(configValue, DEFAULT_TRX_AUTO_COMMIT_TIMEOUT); + } + } + + /** + * This method gets the user specified config for cleaning up dead transactions. Default is 600. + * + * @return int transaction cleanup after value + */ + public static int getTransactionCleanupTimeout() { + VariableKey transactionCleanupTimeoutKey = new VariableKey(TRANSACTION_PACKAGE_ID, + TRANSACTION_CLEANUP_TIMEOUT_KEY, + PredefinedTypes.TYPE_INT, false); + if (!ConfigMap.containsKey(transactionCleanupTimeoutKey)) { + return DEFAULT_TRX_CLEANUP_TIMEOUT; + } else { + Object configValue = ConfigMap.get(transactionCleanupTimeoutKey); + if (configValue == null) { + return DEFAULT_TRX_CLEANUP_TIMEOUT; + } + return parseTimeoutValue(configValue, DEFAULT_TRX_CLEANUP_TIMEOUT); + } + } + + private static int parseTimeoutValue(Object configValue, int defaultValue) { + if (!(configValue instanceof Number number)) { + return defaultValue; + } + int timeoutValue = number.intValue(); + if (timeoutValue <= 0) { + return defaultValue; + } + return timeoutValue; + } + /** * This method will register connection resources with a particular transaction. * diff --git a/bvm/ballerina-runtime/src/main/java/module-info.java b/bvm/ballerina-runtime/src/main/java/module-info.java index 385a49becba8..de8f45222601 100644 --- a/bvm/ballerina-runtime/src/main/java/module-info.java +++ b/bvm/ballerina-runtime/src/main/java/module-info.java @@ -65,7 +65,7 @@ io.ballerina.lang.regexp; exports io.ballerina.runtime.internal.values to io.ballerina.testerina.core, io.ballerina.testerina.runtime, io.ballerina.lang.xml, org.ballerinalang.debugadapter.runtime, io.ballerina.lang.query, - io.ballerina.lang.function, io.ballerina.lang.regexp, io.ballerina.lang.value, io.ballerina.lang.internal; + io.ballerina.lang.function, io.ballerina.lang.regexp, io.ballerina.lang.value, io.ballerina.lang.internal, io.ballerina.lang.array; exports io.ballerina.runtime.internal.configurable to io.ballerina.lang.internal; exports io.ballerina.runtime.internal.types to io.ballerina.lang.typedesc, io.ballerina.testerina.runtime, org.ballerinalang.debugadapter.runtime, io.ballerina.lang.function, io.ballerina.lang.regexp, io.ballerina.testerina.core; diff --git a/bvm/ballerina-runtime/src/main/resources/META-INF/native-image/org.ballerinalang/ballerina-runtime/resource-config.json b/bvm/ballerina-runtime/src/main/resources/META-INF/native-image/org.ballerinalang/ballerina-runtime/resource-config.json index f6ce63367bfd..4b5e81b83ac8 100644 --- a/bvm/ballerina-runtime/src/main/resources/META-INF/native-image/org.ballerinalang/ballerina-runtime/resource-config.json +++ b/bvm/ballerina-runtime/src/main/resources/META-INF/native-image/org.ballerinalang/ballerina-runtime/resource-config.json @@ -3,6 +3,9 @@ "includes": [ { "pattern": "\\QMETA-INF/axiom.xml\\E" + }, + { + "pattern": "resources/.*" } ] }, diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/BuildCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/BuildCommand.java index 6c0ade458d3a..3913a0220103 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/BuildCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/BuildCommand.java @@ -71,6 +71,16 @@ public BuildCommand() { this.offline = true; } + BuildCommand(Path projectPath, PrintStream outStream, PrintStream errStream, boolean exitWhenFinish, + Boolean optimizeDependencyCompilation) { + this.projectPath = projectPath; + this.outStream = outStream; + this.errStream = errStream; + this.exitWhenFinish = exitWhenFinish; + this.optimizeDependencyCompilation = optimizeDependencyCompilation; + this.offline = true; + } + BuildCommand(Path projectPath, PrintStream outStream, PrintStream errStream, boolean exitWhenFinish, boolean dumpBuildTime) { this.projectPath = projectPath; @@ -198,6 +208,10 @@ public BuildCommand() { "generation") private String graalVMBuildOptions; + @CommandLine.Option(names = "--optimize-dependency-compilation", hidden = true, + description = "experimental memory optimization for large projects") + private Boolean optimizeDependencyCompilation; + @Override public void execute() { long start = 0; @@ -317,7 +331,8 @@ private BuildOptions constructBuildOptions() { .setNativeImage(nativeImage) .disableSyntaxTreeCaching(disableSyntaxTreeCaching) .setGraalVMBuildOptions(graalVMBuildOptions) - .setShowDependencyDiagnostics(showDependencyDiagnostics); + .setShowDependencyDiagnostics(showDependencyDiagnostics) + .setOptimizeDependencyCompilation(optimizeDependencyCompilation); if (targetDir != null) { buildOptionsBuilder.targetDir(targetDir.toString()); diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/CommandUtil.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/CommandUtil.java index 270cea14fff2..27212ddfeb2a 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/CommandUtil.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/CommandUtil.java @@ -21,6 +21,7 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import io.ballerina.projects.BuildOptions; import io.ballerina.projects.JBallerinaBackend; import io.ballerina.projects.JvmTarget; import io.ballerina.projects.Package; @@ -94,6 +95,7 @@ import static io.ballerina.projects.util.ProjectUtils.guessPkgName; import static io.ballerina.projects.util.ProjectUtils.initializeProxy; import static java.lang.Runtime.getRuntime; +import static java.nio.file.Files.write; import static org.wso2.ballerinalang.programfile.ProgramFileConstants.ANY_PLATFORM; import static org.wso2.ballerinalang.util.RepoUtils.readSettings; @@ -966,9 +968,9 @@ private static void initLibPackage(Path path, String packageName) throws IOExcep Files.writeString(ballerinaToml, defaultManifest); - // Create README.md - String readmeMd = FileUtils.readFileAsString(NEW_CMD_DEFAULTS + "/" + ProjectConstants.README_MD_FILE_NAME); - Files.writeString(path.resolve(ProjectConstants.README_MD_FILE_NAME), readmeMd); + // Create Package.md + String packageMd = FileUtils.readFileAsString(NEW_CMD_DEFAULTS + "/Package.md"); + write(path.resolve(ProjectConstants.PACKAGE_MD_FILE_NAME), packageMd.getBytes(StandardCharsets.UTF_8)); } /** @@ -1150,12 +1152,14 @@ static String getLatestVersion(List versions) { * @param orgName org name of the dependent package * @param packageName name of the dependent package * @param version version of the dependent package + * @param buildOptions build options {sticky, offline} * @return true if the dependent package compilation has errors */ - static boolean pullDependencyPackages(String orgName, String packageName, String version) { + static boolean pullDependencyPackages(String orgName, String packageName, String version, + BuildOptions buildOptions, String repository) { Path ballerinaUserHomeDirPath = ProjectUtils.createAndGetHomeReposPath(); Path centralRepositoryDirPath = ballerinaUserHomeDirPath.resolve(ProjectConstants.REPOSITORIES_DIR) - .resolve(ProjectConstants.CENTRAL_REPOSITORY_CACHE_NAME); + .resolve(repository); Path balaDirPath = centralRepositoryDirPath.resolve(ProjectConstants.BALA_DIR_NAME); Path balaPath = ProjectUtils.getPackagePath(balaDirPath, orgName, packageName, version); String ballerinaShortVersion = RepoUtils.getBallerinaShortVersion(); @@ -1164,7 +1168,7 @@ static boolean pullDependencyPackages(String orgName, String packageName, String ProjectEnvironmentBuilder defaultBuilder = ProjectEnvironmentBuilder.getDefaultBuilder(); defaultBuilder.addCompilationCacheFactory(new FileSystemCache.FileSystemCacheFactory(cacheDir)); - BalaProject balaProject = BalaProject.loadProject(defaultBuilder, balaPath); + BalaProject balaProject = BalaProject.loadProject(defaultBuilder, balaPath, buildOptions); // Delete package cache if available Path packageCacheDir = cacheDir.resolve(orgName).resolve(packageName).resolve(version); diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/DocCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/DocCommand.java index 795e36ae8282..b7ce4a14fb82 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/DocCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/DocCommand.java @@ -114,6 +114,10 @@ public DocCommand() { "generated by the dependencies") private Boolean showDependencyDiagnostics; + @CommandLine.Option(names = "--optimize-dependency-compilation", hidden = true, + description = "experimental memory optimization for large projects") + private Boolean optimizeDependencyCompilation; + @Override public void execute() { if (this.helpFlag) { @@ -227,8 +231,8 @@ private BuildOptions constructBuildOptions() { .setTestReport(false) .setObservabilityIncluded(false) .disableSyntaxTreeCaching(disableSyntaxTreeCaching) - .setShowDependencyDiagnostics(showDependencyDiagnostics); - + .setShowDependencyDiagnostics(showDependencyDiagnostics) + .setOptimizeDependencyCompilation(optimizeDependencyCompilation); if (targetDir != null) { buildOptionsBuilder.targetDir(targetDir.toString()); diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PackCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PackCommand.java index b73c7ce39e8e..3de702bc97ac 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PackCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PackCommand.java @@ -14,13 +14,9 @@ import io.ballerina.projects.Project; import io.ballerina.projects.ProjectException; import io.ballerina.projects.directory.BuildProject; -import io.ballerina.projects.internal.PackageDiagnostic; -import io.ballerina.projects.internal.ProjectDiagnosticErrorCode; import io.ballerina.projects.util.ProjectConstants; import io.ballerina.toml.semantic.TomlType; import io.ballerina.toml.semantic.ast.TomlTableNode; -import io.ballerina.tools.diagnostics.DiagnosticInfo; -import io.ballerina.tools.diagnostics.DiagnosticSeverity; import org.ballerinalang.toml.exceptions.SettingsTomlException; import org.wso2.ballerinalang.util.RepoUtils; import picocli.CommandLine; @@ -97,6 +93,10 @@ public class PackCommand implements BLauncherCmd { "generated by the dependencies") private Boolean showDependencyDiagnostics; + @CommandLine.Option(names = "--optimize-dependency-compilation", hidden = true, + description = "experimental memory optimization for large projects") + private Boolean optimizeDependencyCompilation; + public PackCommand() { this.projectPath = Paths.get(System.getProperty(ProjectConstants.USER_DIR)); this.outStream = System.out; @@ -115,6 +115,17 @@ public PackCommand() { this.offline = true; } + PackCommand(Path projectPath, PrintStream outStream, PrintStream errStream, boolean exitWhenFinish, + boolean skipCopyLibsFromDist, Boolean optimizeDependencyCompilation) { + this.projectPath = projectPath; + this.outStream = outStream; + this.errStream = errStream; + this.exitWhenFinish = exitWhenFinish; + this.skipCopyLibsFromDist = skipCopyLibsFromDist; + this.optimizeDependencyCompilation = optimizeDependencyCompilation; + this.offline = true; + } + PackCommand(Path projectPath, PrintStream outStream, PrintStream errStream, boolean exitWhenFinish, boolean skipCopyLibsFromDist, Path targetDir) { this.projectPath = projectPath; @@ -244,18 +255,6 @@ public void execute() { // Check package files are modified after last build boolean isPackageModified = isProjectUpdated(project); - // Checks if Package.md is present and issues a warning - Path packageMd = project.sourceRoot().resolve(ProjectConstants.PACKAGE_MD_FILE_NAME); - if (packageMd.toFile().exists()) { - String warning = "The use of Package.md and Module.md is deprecated. " + - "Update the package to add a README.md file.\n"; - DiagnosticInfo diagnosticInfo = new DiagnosticInfo(ProjectDiagnosticErrorCode. - DEPRECATED_DOC_FILE.diagnosticId(), warning, DiagnosticSeverity.WARNING); - PackageDiagnostic packageDiagnostic = new PackageDiagnostic(diagnosticInfo, - project.currentPackage().packageName().toString()); - this.outStream.println(packageDiagnostic); - } - TaskExecutor taskExecutor = new TaskExecutor.TaskBuilder() .addTask(new CleanTargetDirTask(isPackageModified, buildOptions.enableCache()), isSingleFileBuild) .addTask(new RunBuildToolsTask(outStream), isSingleFileBuild) @@ -285,7 +284,8 @@ private BuildOptions constructBuildOptions() { .setConfigSchemaGen(configSchemaGen) .setEnableCache(enableCache) .disableSyntaxTreeCaching(disableSyntaxTreeCaching) - .setShowDependencyDiagnostics(showDependencyDiagnostics); + .setShowDependencyDiagnostics(showDependencyDiagnostics) + .setOptimizeDependencyCompilation(optimizeDependencyCompilation); if (targetDir != null) { buildOptionsBuilder.targetDir(targetDir.toString()); diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/ProfileCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/ProfileCommand.java index cf4a64d2ce43..a962c6a76312 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/ProfileCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/ProfileCommand.java @@ -91,6 +91,10 @@ public class ProfileCommand implements BLauncherCmd { "generated by the dependencies") private Boolean showDependencyDiagnostics; + @CommandLine.Option(names = "--optimize-dependency-compilation", hidden = true, + description = "experimental memory optimization for large projects") + private Boolean optimizeDependencyCompilation; + private static final String PROFILE_CMD = "bal profile [--debug ] []\n "; public ProfileCommand() { @@ -231,7 +235,8 @@ private BuildOptions constructBuildOptions() { .setTestReport(false) .setConfigSchemaGen(configSchemaGen) .disableSyntaxTreeCaching(disableSyntaxTreeCaching) - .setShowDependencyDiagnostics(showDependencyDiagnostics); + .setShowDependencyDiagnostics(showDependencyDiagnostics) + .setOptimizeDependencyCompilation(optimizeDependencyCompilation); if (targetDir != null) { buildOptionsBuilder.targetDir(targetDir.toString()); diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PullCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PullCommand.java index 11f7950c61cc..1b201d6c8c3e 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PullCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PullCommand.java @@ -21,6 +21,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import io.ballerina.cli.BLauncherCmd; +import io.ballerina.projects.BuildOptions; import io.ballerina.projects.JvmTarget; import io.ballerina.projects.ProjectException; import io.ballerina.projects.SemanticVersion; @@ -53,6 +54,7 @@ import static io.ballerina.cli.cmd.Constants.PULL_COMMAND; import static io.ballerina.cli.launcher.LauncherUtils.createLauncherException; import static io.ballerina.projects.util.ProjectConstants.BALA_EXTENSION; +import static io.ballerina.projects.util.ProjectConstants.LOCAL_REPOSITORY_NAME; import static io.ballerina.projects.util.ProjectConstants.PLATFORM; import static io.ballerina.projects.util.ProjectUtils.getAccessTokenOfCLI; import static io.ballerina.projects.util.ProjectUtils.initializeProxy; @@ -72,6 +74,7 @@ public class PullCommand implements BLauncherCmd { private static final String USAGE_TEXT = "bal pull {/ | /:}"; + private final PrintStream outStream; private final PrintStream errStream; private final boolean exitWhenFinish; @@ -87,14 +90,23 @@ public class PullCommand implements BLauncherCmd { @CommandLine.Option(names = "--repository") private String repositoryName; + @CommandLine.Option(names = "--sticky", hidden = true, defaultValue = "true") + private boolean sticky; + + @CommandLine.Option(names = "--offline", hidden = true) + private boolean offline; + public PullCommand() { + this.outStream = System.out; this.errStream = System.err; this.exitWhenFinish = true; } public PullCommand(PrintStream errStream, boolean exitWhenFinish) { + this.outStream = errStream; this.errStream = errStream; this.exitWhenFinish = exitWhenFinish; + this.repositoryName = null; } @Override @@ -132,7 +144,7 @@ public void execute() { // Get org name String[] moduleInfo = resourceName.split("/"); if (moduleInfo.length != 2) { - CommandUtil.printError(errStream, "invalid package name. Provide the package name with the organization.", + CommandUtil.printError(errStream, "invalid package. Provide the package name with the organization.", USAGE_TEXT, false); CommandUtil.exitError(this.exitWhenFinish); return; @@ -149,7 +161,7 @@ public void execute() { packageName = moduleNameAndVersion; version = Names.EMPTY.getValue(); } else { - CommandUtil.printError(errStream, "invalid package name. Provide the package name with the organization.", + CommandUtil.printError(errStream, "invalid package. Provide the package name with the organization.", USAGE_TEXT, false); CommandUtil.exitError(this.exitWhenFinish); return; @@ -186,13 +198,77 @@ public void execute() { settings = Settings.from(); } + if (repositoryName == null) { + repositoryName = ProjectConstants.CENTRAL_REPOSITORY_CACHE_NAME; + version = pullFromCentral(settings, orgName, packageName, version); + } else if (!LOCAL_REPOSITORY_NAME.equals(repositoryName)) { + pullFromMavenRepo(settings, orgName, packageName, version); + } + + if (!resolveDependencies(orgName, packageName, version)) { + CommandUtil.exitError(this.exitWhenFinish); + return; + } + + if (this.exitWhenFinish) { + Runtime.getRuntime().exit(0); + } + + } + + private String pullFromCentral(Settings settings, String orgName, String packageName, String version) { + Path packagePathInBalaCache = ProjectUtils.createAndGetHomeReposPath() + .resolve(ProjectConstants.REPOSITORIES_DIR).resolve(ProjectConstants.CENTRAL_REPOSITORY_CACHE_NAME) + .resolve(ProjectConstants.BALA_DIR_NAME) + .resolve(orgName).resolve(packageName); + + if (!version.equals(Names.EMPTY.getValue()) && Files.exists(packagePathInBalaCache.resolve(version))) { + outStream.println("Package already exists.\n"); + } + // create directory path in bala cache + try { + createDirectories(packagePathInBalaCache); + } catch (IOException e) { + CommandUtil.exitError(this.exitWhenFinish); + throw createLauncherException( + "unexpected error occurred while creating package repository in bala cache: " + e.getMessage()); + } + + CommandUtil.setPrintStream(errStream); + String supportedPlatform = Arrays.stream(JvmTarget.values()) + .map(JvmTarget::code) + .collect(Collectors.joining(",")); + CentralAPIClient client; + try { + client = new CentralAPIClient(RepoUtils.getRemoteRepoURL(), + initializeProxy(settings.getProxy()), settings.getProxy().username(), + settings.getProxy().password(), getAccessTokenOfCLI(settings), + settings.getCentral().getConnectTimeout(), + settings.getCentral().getReadTimeout(), settings.getCentral().getWriteTimeout(), + settings.getCentral().getCallTimeout(), settings.getCentral().getMaxRetries()); + client.pullPackage(orgName, packageName, version, packagePathInBalaCache, supportedPlatform, + RepoUtils.getBallerinaVersion(), false); + if (version.equals(Names.EMPTY.getValue())) { + List versions = client.getPackageVersions(orgName, packageName, supportedPlatform, + RepoUtils.getBallerinaVersion()); + version = CommandUtil.getLatestVersion(versions); + } + } catch (PackageAlreadyExistsException e) { + outStream.println("Package already exists.\n"); + version = e.version(); + } catch (CentralClientException e) { + errStream.println("package not found: " + orgName + "/" + packageName); + CommandUtil.exitError(this.exitWhenFinish); + } + return version; + } + + private void pullFromMavenRepo(Settings settings, String orgName, String packageName, String version) { Repository targetRepository = null; - if (repositoryName != null) { - for (Repository repository : settings.getRepositories()) { - if (repositoryName.equals(repository.id())) { - targetRepository = repository; - break; - } + for (Repository repository : settings.getRepositories()) { + if (repositoryName.equals(repository.id())) { + targetRepository = repository; + break; } } @@ -218,7 +294,8 @@ public void execute() { Path mavenBalaCachePath = RepoUtils.createAndGetHomeReposPath() .resolve(ProjectConstants.REPOSITORIES_DIR) .resolve(targetRepository.id()) - .resolve(ProjectConstants.BALA_DIR_NAME); + .resolve(ProjectConstants.BALA_DIR_NAME) + .resolve(orgName).resolve(packageName).resolve(version); try { Path tmpDownloadDirectory = Files.createTempDirectory("ballerina-" + System.nanoTime()); @@ -233,8 +310,7 @@ public void execute() { try (BufferedReader bufferedReader = Files.newBufferedReader(packageJsonPath, StandardCharsets.UTF_8)) { JsonObject resultObj = new Gson().fromJson(bufferedReader, JsonObject.class); String platform = resultObj.get(PLATFORM).getAsString(); - Path actualBalaPath = mavenBalaCachePath.resolve(orgName).resolve(packageName) - .resolve(version).resolve(platform); + Path actualBalaPath = mavenBalaCachePath.resolve(platform); org.apache.commons.io.FileUtils.copyDirectory(temporaryExtractionPath.toFile(), actualBalaPath.toFile()); } @@ -247,56 +323,24 @@ public void execute() { } PrintStream out = System.out; out.println("Successfully pulled the package from the custom repository."); - return; - } - - Path packagePathInBalaCache = ProjectUtils.createAndGetHomeReposPath() - .resolve(ProjectConstants.REPOSITORIES_DIR).resolve(ProjectConstants.CENTRAL_REPOSITORY_CACHE_NAME) - .resolve(ProjectConstants.BALA_DIR_NAME) - .resolve(orgName).resolve(packageName); - // create directory path in bala cache - try { - createDirectories(packagePathInBalaCache); - } catch (IOException e) { - CommandUtil.exitError(this.exitWhenFinish); - throw createLauncherException( - "unexpected error occurred while creating package repository in bala cache: " + e.getMessage()); } + } + private boolean resolveDependencies(String orgName, String packageName, String version) { CommandUtil.setPrintStream(errStream); - String supportedPlatform = Arrays.stream(JvmTarget.values()) - .map(JvmTarget::code) - .collect(Collectors.joining(",")); try { - CentralAPIClient client = new CentralAPIClient(RepoUtils.getRemoteRepoURL(), - initializeProxy(settings.getProxy()), settings.getProxy().username(), - settings.getProxy().password(), getAccessTokenOfCLI(settings), - settings.getCentral().getConnectTimeout(), - settings.getCentral().getReadTimeout(), settings.getCentral().getWriteTimeout(), - settings.getCentral().getCallTimeout(), settings.getCentral().getMaxRetries()); - client.pullPackage(orgName, packageName, version, packagePathInBalaCache, supportedPlatform, - RepoUtils.getBallerinaVersion(), false); - if (version.equals(Names.EMPTY.getValue())) { - List versions = client.getPackageVersions(orgName, packageName, supportedPlatform, - RepoUtils.getBallerinaVersion()); - version = CommandUtil.getLatestVersion(versions); - } - boolean hasCompilationErrors = CommandUtil.pullDependencyPackages(orgName, packageName, version); + BuildOptions buildOptions = BuildOptions.builder().setSticky(sticky).setOffline(offline).build(); + boolean hasCompilationErrors = CommandUtil.pullDependencyPackages( + orgName, packageName, version, buildOptions, repositoryName); if (hasCompilationErrors) { CommandUtil.printError(this.errStream, "compilation contains errors", null, false); - CommandUtil.exitError(this.exitWhenFinish); - return; + return false; } - } catch (PackageAlreadyExistsException e) { - errStream.println(e.getMessage()); - CommandUtil.exitError(this.exitWhenFinish); - } catch (CentralClientException e) { - errStream.println("package not found: " + orgName + "/" + packageName); - CommandUtil.exitError(this.exitWhenFinish); - } - if (this.exitWhenFinish) { - Runtime.getRuntime().exit(0); + } catch (ProjectException e) { + CommandUtil.printError(this.errStream, + "error occurred while resolving dependencies, reason: " + e.getMessage(), null, false); } + return true; } @Override diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PushCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PushCommand.java index 64f5b784d29b..e56bca657100 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PushCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PushCommand.java @@ -69,7 +69,6 @@ import static io.ballerina.projects.util.ProjectConstants.LOCAL_TOOLS_JSON; import static io.ballerina.projects.util.ProjectConstants.SETTINGS_FILE_NAME; import static io.ballerina.projects.util.ProjectUtils.getAccessTokenOfCLI; -import static io.ballerina.projects.util.ProjectUtils.getPackageNameFromBalaName; import static io.ballerina.projects.util.ProjectUtils.initializeProxy; import static io.ballerina.runtime.api.constants.RuntimeConstants.SYSTEM_PROP_BAL_DEBUG; @@ -359,33 +358,21 @@ private static Path validateBalaFile(BuildProject project, Path customBalaPath) } private static void validatePackageMdAndBalToml(Path balaPath) { - // Gets package name from balapath - String packageName = getPackageNameFromBalaName(balaPath.toFile().getName()); - try (ZipInputStream zip = new ZipInputStream(Files.newInputStream(balaPath, StandardOpenOption.READ))) { ZipEntry entry; while ((entry = zip.getNextEntry()) != null) { - // Checks if Package.md or README.md exists and is not empty if (entry.getName().equals( ProjectConstants.BALA_DOCS_DIR + "/" + ProjectConstants.PACKAGE_MD_FILE_NAME)) { if (entry.getSize() == 0) { throw new ProjectException(ProjectConstants.PACKAGE_MD_FILE_NAME + " cannot be empty."); } return; - } else if (entry.getName().equals( - ProjectConstants.BALA_DOCS_DIR + "/" + ProjectConstants.MODULES_ROOT + "/" - + packageName + "/" + ProjectConstants.README_MD_FILE_NAME)) { - if (entry.getSize() == 0) { - throw new ProjectException(ProjectConstants.README_MD_FILE_NAME + " cannot be empty."); - } - return; } } } catch (IOException e) { throw new ProjectException("error while validating the bala file: " + e.getMessage(), e); } - // Throws an exception if both files aren't present - throw new ProjectException(ProjectConstants.README_MD_FILE_NAME + " is missing in bala file:" + balaPath); + throw new ProjectException(ProjectConstants.PACKAGE_MD_FILE_NAME + " is missing in bala file:" + balaPath); } private void pushBalaToCustomRepo(Path balaFilePath) { diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/RunCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/RunCommand.java index 3c8fa2d50983..f97fd8a383bc 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/RunCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/RunCommand.java @@ -29,6 +29,7 @@ import io.ballerina.cli.task.RunExecutableTask; import io.ballerina.cli.utils.BuildTime; import io.ballerina.cli.utils.FileUtils; +import io.ballerina.cli.utils.ProjectWatcher; import io.ballerina.projects.BuildOptions; import io.ballerina.projects.Project; import io.ballerina.projects.ProjectException; @@ -37,6 +38,7 @@ import io.ballerina.projects.directory.SingleFileProject; import io.ballerina.projects.internal.model.Target; import io.ballerina.projects.util.ProjectConstants; +import io.ballerina.projects.util.ProjectUtils; import picocli.CommandLine; import java.io.IOException; @@ -66,6 +68,8 @@ public class RunCommand implements BLauncherCmd { private final PrintStream errStream; private Path projectPath; private boolean exitWhenFinish; + RunExecutableTask runExecutableTask; + Project project; private static final PathMatcher JAR_EXTENSION_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.jar"); @@ -83,6 +87,8 @@ public class RunCommand implements BLauncherCmd { @CommandLine.Option(names = "--debug", hidden = true) private String debugPort; + @CommandLine.Option(names = "--watch", description = "watch for file changes and automatically re-run the project") + private boolean watch; @CommandLine.Option(names = "--dump-bir", hidden = true) private boolean dumpBIR; @@ -125,6 +131,10 @@ public class RunCommand implements BLauncherCmd { "generated by the dependencies") private Boolean showDependencyDiagnostics; + @CommandLine.Option(names = "--optimize-dependency-compilation", hidden = true, + description = "experimental memory optimization for large projects") + private Boolean optimizeDependencyCompilation; + private static final String runCmd = """ bal run [--debug ] \s @@ -145,6 +155,14 @@ public RunCommand() { this.errStream = outStream; this.offline = true; } + RunCommand(Path projectPath, PrintStream outStream, boolean exitWhenFinish, Boolean optimizeDependencyCompilation) { + this.projectPath = projectPath; + this.exitWhenFinish = exitWhenFinish; + this.outStream = outStream; + this.errStream = outStream; + this.optimizeDependencyCompilation = optimizeDependencyCompilation; + this.offline = true; + } RunCommand(Path projectPath, PrintStream outStream, boolean exitWhenFinish, Path targetDir) { this.projectPath = projectPath; @@ -200,8 +218,22 @@ public void execute() { sticky = false; } + if (this.watch) { + try { + ProjectWatcher projectWatcher = new ProjectWatcher( + this, Paths.get(this.projectPath.toString()), outStream); + projectWatcher.watch(); + } catch (IOException e) { + throw createLauncherException("unable to watch the project:" + e.getMessage()); + } catch (ProjectException e) { + CommandUtil.printError(this.errStream, e.getMessage(), runCmd, false); + CommandUtil.exitError(this.exitWhenFinish); + } + return; + } + + // load project - Project project; BuildOptions buildOptions = constructBuildOptions(); boolean isSingleFileBuild = false; @@ -266,7 +298,7 @@ public void execute() { isPackageModified, buildOptions.enableCache())) // .addTask(new CopyResourcesTask(), isSingleFileBuild) .addTask(new CreateExecutableTask(outStream, null, target, true)) - .addTask(new RunExecutableTask(args, outStream, errStream, target)) + .addTask(runExecutableTask = new RunExecutableTask(args, outStream, errStream, target)) .addTask(new DumpBuildTimeTask(outStream), !project.buildOptions().dumpBuildTime()) .build(); taskExecutor.executeTasks(project); @@ -295,6 +327,16 @@ public void printUsage(StringBuilder out) { public void setParentCmdParser(CommandLine parentCmdParser) { } + public void unsetWatch() { + this.watch = false; + } + + public void killProcess() { + if (runExecutableTask != null) { + runExecutableTask.killProcess(); + } + } + private BuildOptions constructBuildOptions() { BuildOptions.BuildOptionsBuilder buildOptionsBuilder = BuildOptions.builder(); @@ -311,7 +353,8 @@ private BuildOptions constructBuildOptions() { .setConfigSchemaGen(configSchemaGen) .disableSyntaxTreeCaching(disableSyntaxTreeCaching) .setDumpBuildTime(dumpBuildTime) - .setShowDependencyDiagnostics(showDependencyDiagnostics); + .setShowDependencyDiagnostics(showDependencyDiagnostics) + .setOptimizeDependencyCompilation(optimizeDependencyCompilation); if (targetDir != null) { buildOptionsBuilder.targetDir(targetDir.toString()); @@ -319,4 +362,8 @@ private BuildOptions constructBuildOptions() { return buildOptionsBuilder.build(); } + + public boolean containsService() { + return project == null || ProjectUtils.containsDefaultModuleService(project.currentPackage()); + } } diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/TestCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/TestCommand.java index 7fafc53afdcf..c14799723c88 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/TestCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/TestCommand.java @@ -80,6 +80,15 @@ public TestCommand() { this.offline = true; } + TestCommand(Path projectPath, boolean exitWhenFinish, Boolean optimizeDependencyCompilation) { + this.projectPath = projectPath; + this.optimizeDependencyCompilation = optimizeDependencyCompilation; + this.outStream = System.out; + this.errStream = System.err; + this.exitWhenFinish = exitWhenFinish; + this.offline = true; + } + TestCommand(Path projectPath, PrintStream outStream, PrintStream errStream, boolean exitWhenFinish) { this.projectPath = projectPath; this.outStream = outStream; @@ -89,7 +98,8 @@ public TestCommand() { } TestCommand(Path projectPath, PrintStream outStream, PrintStream errStream, boolean exitWhenFinish, - Boolean testReport, Boolean coverage, String coverageFormat) { + Boolean testReport, Boolean coverage, String coverageFormat, + Boolean optimizeDependencyCompilation) { this.projectPath = projectPath; this.outStream = outStream; this.errStream = errStream; @@ -97,6 +107,7 @@ public TestCommand() { this.testReport = testReport; this.coverage = coverage; this.coverageFormat = coverageFormat; + this.optimizeDependencyCompilation = optimizeDependencyCompilation; this.offline = true; } @@ -209,6 +220,9 @@ public TestCommand() { @CommandLine.Option(names = "--cloud", description = "Enable cloud artifact generation") private String cloud; + @CommandLine.Option(names = "--optimize-dependency-compilation", hidden = true, + description = "experimental memory optimization for large projects") + private Boolean optimizeDependencyCompilation; private static final String testCmd = "bal test [--OPTIONS]\n" + " [ | ] [(-Ckey=value)...]"; @@ -415,7 +429,8 @@ private BuildOptions constructBuildOptions() { .setEnableCache(enableCache) .disableSyntaxTreeCaching(disableSyntaxTreeCaching) .setGraalVMBuildOptions(graalVMBuildOptions) - .setShowDependencyDiagnostics(showDependencyDiagnostics); + .setShowDependencyDiagnostics(showDependencyDiagnostics) + .setOptimizeDependencyCompilation(optimizeDependencyCompilation); if (targetDir != null) { buildOptionsBuilder.targetDir(targetDir.toString()); diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/launcher/LauncherUtils.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/launcher/LauncherUtils.java index 11441741882b..17cb285e382e 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/launcher/LauncherUtils.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/launcher/LauncherUtils.java @@ -28,6 +28,8 @@ import java.io.IOException; import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; @@ -85,14 +87,21 @@ public static BLauncherException createLauncherException(String errorPrefix, Thr if (cause instanceof BError) { message = ((BError) cause).getPrintableStackTrace(); } else { - message = cause.toString(); + StringWriter sw = new StringWriter(); + cause.printStackTrace(new PrintWriter(sw)); + message = sw.toString(); } BLauncherException launcherException = new BLauncherException(); launcherException.addMessage("error: " + errorPrefix + message); return launcherException; } - static void printLauncherException(BLauncherException e, PrintStream outStream) { + + public static String prepareCompilerErrorMessage(String message) { + return "error: " + LauncherUtils.makeFirstLetterLowerCase(message); + } + + public static void printLauncherException(BLauncherException e, PrintStream outStream) { List errorMessages = e.getMessages(); errorMessages.forEach(outStream::println); } diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/launcher/Main.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/launcher/Main.java index 4a06b6b3bc1c..292f42f2bb0f 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/launcher/Main.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/launcher/Main.java @@ -38,6 +38,7 @@ import static io.ballerina.cli.cmd.Constants.HELP_OPTION; import static io.ballerina.cli.cmd.Constants.HELP_SHORT_OPTION; import static io.ballerina.cli.cmd.Constants.VERSION_COMMAND; +import static io.ballerina.cli.launcher.LauncherUtils.prepareCompilerErrorMessage; /** * This class executes a Ballerina program. @@ -208,10 +209,6 @@ private static void printBallerinaDistPath() { } } - private static String prepareCompilerErrorMessage(String message) { - return "error: " + LauncherUtils.makeFirstLetterLowerCase(message); - } - private static String getFirstUnknownArg(String errorMessage) { String optionsString = errorMessage.split(":")[1]; return (optionsString.split(","))[0].trim(); diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/CompileTask.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/CompileTask.java index 414485188df8..eee746244286 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/CompileTask.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/CompileTask.java @@ -219,6 +219,11 @@ public void execute(Project project) { if (project.buildOptions().dumpBuildTime()) { start = System.currentTimeMillis(); } + + String projectLoadingDiagnostic = ProjectUtils.getProjectLoadingDiagnostic(); + if (projectLoadingDiagnostic != null && !projectLoadingDiagnostic.isEmpty()) { + out.println(projectLoadingDiagnostic); + } PackageCompilation packageCompilation = project.currentPackage().getCompilation(); if (project.buildOptions().dumpBuildTime()) { BuildTime.getInstance().packageCompilationDuration = System.currentTimeMillis() - start; diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunExecutableTask.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunExecutableTask.java index 8030f13f2844..4a8aeda17573 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunExecutableTask.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunExecutableTask.java @@ -46,6 +46,7 @@ public class RunExecutableTask implements Task { private final transient PrintStream out; private final transient PrintStream err; private final Target target; + private Process process; /** * Create a task to run the executable. This requires {@link CreateExecutableTask} to be completed. @@ -97,15 +98,24 @@ private void runGeneratedExecutable(Project project) { .normalize().toString()); commands.addAll(args); ProcessBuilder pb = new ProcessBuilder(commands).inheritIO(); - Process process = pb.start(); + process = pb.start(); process.waitFor(); int exitValue = process.exitValue(); if (exitValue != 0) { throw new RuntimePanicException(exitValue); } - } catch (IOException | InterruptedException e) { + } catch (IOException e) { throw createLauncherException("Error occurred while running the executable ", e.getCause()); + } catch (InterruptedException e) { + if (process != null && process.isAlive()) { + process.destroy(); + } } } -} + public void killProcess() { + if (process != null && process.isAlive()) { + process.destroy(); + } + } +} diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunNativeImageTestTask.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunNativeImageTestTask.java index 1562eadd0659..645b8b5b6189 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunNativeImageTestTask.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunNativeImageTestTask.java @@ -283,7 +283,7 @@ public void execute(Project project) { } } catch (IOException e) { TestUtils.cleanTempCache(project, cachesRoot); - throw createLauncherException("error occurred while running tests", e); + throw createLauncherException("error occurred while running tests: ", e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } @@ -355,9 +355,8 @@ private int runTestSuiteWithNativeImage(Package currentPackage, Target target, nativeArgs.addAll(Lists.of("-cp", classPath)); if (currentPackage.project().kind() == ProjectKind.SINGLE_FILE_PROJECT) { - String[] splittedArray = currentPackage.project().sourceRoot().toString(). - replace(ProjectConstants.BLANG_SOURCE_EXT, "").split("/"); - packageName = splittedArray[splittedArray.length - 1]; + packageName = currentPackage.project().sourceRoot().getFileName().toString() + .replace(ProjectConstants.BLANG_SOURCE_EXT, ""); validateResourcesWithinJar(testSuiteMap, packageName); } else if (testSuiteMap.size() == 1) { packageName = (testSuiteMap.values().toArray(new TestSuite[0])[0]).getPackageID(); diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/ProjectWatcher.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/ProjectWatcher.java new file mode 100644 index 000000000000..d3cbd0edae6d --- /dev/null +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/ProjectWatcher.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.cli.utils; + +import io.ballerina.cli.cmd.RunCommand; +import io.ballerina.projects.ProjectKind; +import io.ballerina.projects.internal.ProjectFiles; +import io.ballerina.projects.util.FileUtils; + +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static io.ballerina.projects.util.ProjectConstants.BLANG_SOURCE_EXT; +import static io.ballerina.projects.util.ProjectConstants.DEPENDENCIES_TOML; +import static io.ballerina.projects.util.ProjectConstants.MODULES_ROOT; +import static io.ballerina.projects.util.ProjectConstants.RESOURCE_DIR_NAME; +import static io.ballerina.projects.util.ProjectConstants.TOML_EXTENSION; +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; +import static java.nio.file.StandardWatchEventKinds.OVERFLOW; + +/** + * Represents a watcher that looks into project file changes. + * + * @since 2201.10.0 + */ +public class ProjectWatcher { + private final WatchService fileWatcher; + private final Map watchKeys; + private final RunCommand runCommand; + private final PrintStream outStream; + private final Path projectPath; + private final ProjectKind projectKind; + private final ScheduledExecutorService scheduledExecutorService; + private final Map debounceMap = new ConcurrentHashMap<>(); + private static final long debounceTimeMillis = 250; + private final RunCommandExecutor[] thread; + private volatile boolean forceStop = false; + + public ProjectWatcher(RunCommand runCommand, Path projectPath, PrintStream outStream) throws IOException { + this.fileWatcher = FileSystems.getDefault().newWatchService(); + this.runCommand = runCommand; + thread = new RunCommandExecutor[]{new RunCommandExecutor(runCommand, outStream)}; + this.projectPath = projectPath.toAbsolutePath(); + this.outStream = outStream; + this.watchKeys = new HashMap<>(); + this.projectKind = deriveProjectKind(); + validateProjectPath(); + this.scheduledExecutorService = Executors.newScheduledThreadPool(1); + registerFileTree(projectPath); + } + + /** + * Watches for any file changes in a Ballerina service project and restarts the service. + * Changes on source files, resources and .toml files are considered valid file changes. Changes to + * Dependencies.toml, tests, target directory and other files are ignored. + * + * @throws IOException if the watcher cannot register files for watching. + */ + public void watch() throws IOException { // TODO: find out why panics and removing service doesn't exit the code + thread[0].start(); + while (thread[0].shouldWatch() && !forceStop) { + WatchKey key; + key = fileWatcher.poll(); + Path dir = watchKeys.get(key); + if (dir == null) { + continue; + } + for (WatchEvent event : key.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + if (kind == OVERFLOW) { + continue; + } + WatchEvent pathWatchEvent = cast(event); + Path changedFileName = pathWatchEvent.context(); + Path changedFilePath = dir.resolve(changedFileName).toAbsolutePath(); + if (isValidFileChange(changedFilePath)) { + long currentTime = System.currentTimeMillis(); + debounceMap.put(changedFilePath, currentTime); + scheduledExecutorService.schedule(() -> { + Long lastModifiedTime = debounceMap.get(changedFilePath); + if (lastModifiedTime == null + || (System.currentTimeMillis() - lastModifiedTime < debounceTimeMillis)) { + return; + } + outStream.println("\nDetected file changes. Re-running the project..."); + thread[0].terminate(); + waitForRunCmdThreadToJoin(); + thread[0] = new RunCommandExecutor(runCommand, outStream); + thread[0].start(); + debounceMap.remove(changedFilePath); + }, debounceTimeMillis, TimeUnit.MILLISECONDS); + } + if (kind == ENTRY_CREATE && Files.isDirectory(changedFilePath)) { + registerFileTree(changedFilePath); + } + } + boolean valid = key.reset(); + if (!valid) { + watchKeys.remove(key); + if (watchKeys.isEmpty()) { + break; + } + } + } + waitForRunCmdThreadToJoin(); + } + + public void stopWatching() { + try { + if (thread != null) { + thread[0].terminate(); + thread[0].join(); + } + forceStop = true; + fileWatcher.close(); + } catch (IOException | InterruptedException e) { + outStream.println("Error occurred while stopping the project watcher: " + e.getMessage()); + } + } + + private ProjectKind deriveProjectKind() { + return FileUtils.hasExtension(this.projectPath) ? ProjectKind.SINGLE_FILE_PROJECT : ProjectKind.BUILD_PROJECT; + } + + private boolean isValidFileChange(Path path) { + // If single file project, we only consider changes to the file itself + if (projectKind.equals(ProjectKind.SINGLE_FILE_PROJECT)) { + return projectPath.equals(path); + } + path = projectPath.relativize(path); + if (Files.isDirectory(path)) { // We ignore the directory changes + return false; + } + Path fileNamePath = path.getFileName(); + if (fileNamePath == null) { + return false; + } + String fileName = fileNamePath.toString(); + if (path.getNameCount() == 1) { + // Files (not directories) immediately in the root directory + if (fileName.endsWith(BLANG_SOURCE_EXT)) { + return true; + } + return fileName.endsWith(TOML_EXTENSION) && !fileName.equals(DEPENDENCIES_TOML); + } + if (path.startsWith(RESOURCE_DIR_NAME) && path.getNameCount() > 1) { + // name count > 1 to avoid files with resources prefix + return true; + } + if (path.startsWith(MODULES_ROOT) && path.getNameCount() > 2) { + // changes within submodules + // name count > 2 means the path is in a submodule (modules/) + Path modulePath = path.subpath(2, path.getNameCount()); + if (modulePath.getNameCount() == 1 && fileName.endsWith(BLANG_SOURCE_EXT)) { + return true; + } + return modulePath.startsWith(RESOURCE_DIR_NAME); + } + return false; + } + + private void registerFileTree(Path path) throws IOException { + if (projectKind.equals(ProjectKind.SINGLE_FILE_PROJECT)) { + Path parentPath = path.toAbsolutePath().getParent(); + if (parentPath != null) { + register(parentPath); + } + return; + } + Files.walkFileTree(path, new SimpleFileVisitor<>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + register(dir); + return FileVisitResult.CONTINUE; + } + }); + } + + private void register(Path dir) throws IOException { + WatchKey key = dir.register(fileWatcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); + watchKeys.put(key, dir); + } + + private void validateProjectPath() { + if (projectKind.equals(ProjectKind.SINGLE_FILE_PROJECT)) { + ProjectFiles.validateSingleFileProjectFilePath(projectPath); + } else { + ProjectFiles.validateBuildProjectDirPath(projectPath); + } + } + + @SuppressWarnings("unchecked") + private static WatchEvent cast(WatchEvent event) { + return (WatchEvent) event; + } + + private void waitForRunCmdThreadToJoin() { + try { + thread[0].join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/RunCommandExecutor.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/RunCommandExecutor.java new file mode 100644 index 000000000000..803f4f61b6fb --- /dev/null +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/RunCommandExecutor.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.cli.utils; + +import io.ballerina.cli.cmd.RunCommand; +import io.ballerina.cli.launcher.BLauncherException; +import io.ballerina.cli.launcher.LauncherUtils; +import io.ballerina.cli.launcher.RuntimePanicException; +import io.ballerina.runtime.internal.util.RuntimeUtils; +import org.ballerinalang.compiler.BLangCompilerException; + +import java.io.PrintStream; + +/** + * Represents a wrapper for the RunCommand to be run in a different thread. + * + * @since 2201.10.0 + */ +public class RunCommandExecutor extends Thread { + private static final String COMPILATION_ERROR_MESSAGE = "compilation contains errors"; + + private final RunCommand runCommand; + private final PrintStream outStream; + private volatile boolean runtimePanic; + + public RunCommandExecutor(RunCommand runCommand, PrintStream outStream) { + this.runCommand = runCommand; + this.outStream = outStream; + this.runtimePanic = false; + } + + public void run() { + // We use the original runCommand instance with the watch field set to false. That will preserve all the + // build options passed by the developer. + try { + runCommand.unsetWatch(); + runCommand.execute(); + } catch (BLangCompilerException e) { + if (!(e.getMessage().contains(COMPILATION_ERROR_MESSAGE))) { + // print the error message only if the exception was not thrown due to compilation errors + outStream.println(LauncherUtils.prepareCompilerErrorMessage(e.getMessage())); + } + // These are compiler errors, and are already logged. Hence simply exit. + } catch (BLauncherException e) { + LauncherUtils.printLauncherException(e, outStream); + } catch (RuntimePanicException ignored) { + runtimePanic = true; + } catch (Throwable e) { + RuntimeUtils.logBadSad(e); + runtimePanic = true; + } + } + + public synchronized void terminate() { + runCommand.killProcess(); + this.interrupt(); + } + + public synchronized boolean shouldWatch() { + return runCommand.containsService() && !runtimePanic; + } +} diff --git a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-build.help b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-build.help index 4c3cb1f107e0..6c7accdc1a5a 100755 --- a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-build.help +++ b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-build.help @@ -67,6 +67,11 @@ OPTIONS Print the diagnostics that are related to the dependencies. By default, these diagnostics are not printed to the console. + --optimize-dependency-compilation + [EXPERIMENTAL] Enables memory-efficient compilation of package dependencies + using separate processes. This can help prevent out-of-memory issues during + the initial compilation with a clean central cache. + EXAMPLES Build the current package. This will generate an 'app.jar' file in the diff --git a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-doc.help b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-doc.help index 4b31437f530d..55f31f2172b2 100755 --- a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-doc.help +++ b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-doc.help @@ -34,6 +34,11 @@ OPTIONS Print the diagnostics that are related to the dependencies. By default, these diagnostics are not printed to the console. + --optimize-dependency-compilation + [EXPERIMENTAL] Enables memory-efficient compilation of package dependencies + using separate processes. This can help prevent out-of-memory issues during + the initial compilation with a clean central cache. + EXAMPLES Generate API documentation for the current package. diff --git a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-openapi.help b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-openapi.help index 736df2e1061f..de61e7e7cf49 100755 --- a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-openapi.help +++ b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-openapi.help @@ -13,7 +13,8 @@ SYNOPSIS [--operations ] [-n | --nullable] [--license] [--with-tests] [--client-methods] [--without-data-binding] - [--status-code-binding] + [--status-code-binding] [--mock] [--with-service-contract] + [--single-file] [--use-sanitized-oas] bal openapi [-i | --input] [--json] [-s | --service] @@ -93,6 +94,22 @@ OPTIONS This option can be used in the client generation to generate the client methods with status code response binding. + --mock + This option can be used in the client generation to generate a mock + client for the given OpenAPI contract. + + --with-service-contract + This option can be used to generate the service contract type for the + given OpenAPI contract. + + --single-file + This option can be used to generate the Ballerina service or client + with related types and utility functions in a single file. + + --use-sanitized-oas + This is an experimental feature. This option enables service/client code + generation by modifying the given OAS to follow the Ballerina language + best practices. EXAMPLES Generate a Ballerina mock service using a `hello.yaml` OpenAPI contract. $ bal openapi -i hello.yaml --mode service diff --git a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-pack.help b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-pack.help index 7dee174553a9..3e1caaea194d 100644 --- a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-pack.help +++ b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-pack.help @@ -28,6 +28,11 @@ OPTIONS Print the diagnostics that are related to the dependencies. By default, these diagnostics are not printed to the console. + --optimize-dependency-compilation + [EXPERIMENTAL] Enables memory-efficient compilation of package dependencies + using separate processes. This can help prevent out-of-memory issues during + the initial compilation with a clean central cache. + EXAMPLES Pack the current package into .bala file. diff --git a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-profile.help b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-profile.help index 200bcdedc083..1102458b4155 100755 --- a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-profile.help +++ b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-profile.help @@ -23,6 +23,11 @@ OPTIONS Print the diagnostics that are related to the dependencies. By default, these diagnostics are not printed to the console. + --optimize-dependency-compilation + [EXPERIMENTAL] Enables memory-efficient compilation of package dependencies + using separate processes. This can help prevent out-of-memory issues during + the initial compilation with a clean central cache. + EXAMPLES Run Ballerina profiler on the 'main' function and service(s) in the 'app.bal' file. $ bal profile app.bal diff --git a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-run.help b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-run.help index e28259f9641d..73cab58bec7f 100755 --- a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-run.help +++ b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-run.help @@ -60,6 +60,15 @@ OPTIONS Print the diagnostics that are related to the dependencies. By default, these diagnostics are not printed to the console. + --optimize-dependency-compilation + [EXPERIMENTAL] Enables memory-efficient compilation of package dependencies + using separate processes. This can help prevent out-of-memory issues during + the initial compilation with a clean central cache. + + --watch + [Experimental] Automatically re-run Ballerina service projects upon file + changes. + ARGUMENTS -- diff --git a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-test.help b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-test.help index e2e9ad7bb4b5..5263042e1227 100755 --- a/cli/ballerina-cli/src/main/resources/cli-help/ballerina-test.help +++ b/cli/ballerina-cli/src/main/resources/cli-help/ballerina-test.help @@ -117,6 +117,11 @@ OPTIONS - target value should be added as : "/home/ballerina/". should be the relative path to the package. + --optimize-dependency-compilation + [EXPERIMENTAL] Enables memory-efficient compilation of package dependencies + using separate processes. This can help prevent out-of-memory issues during + the initial compilation with a clean central cache. + ARGUMENTS (-Ckey=value)... diff --git a/cli/ballerina-cli/src/main/resources/create_cmd_templates/lib/Module.md b/cli/ballerina-cli/src/main/resources/create_cmd_templates/lib/Module.md new file mode 100644 index 000000000000..8a69f51930aa --- /dev/null +++ b/cli/ballerina-cli/src/main/resources/create_cmd_templates/lib/Module.md @@ -0,0 +1,6 @@ +Prints "Hello, World!" with a main function. +[//]: # (above is the module summary) + +# Module Overview +Provides an overview about the module when generating the API documentations. +For example, refer to https://lib.ballerina.io/ballerina/io/latest diff --git a/cli/ballerina-cli/src/main/resources/new_cmd_defaults/Package.md b/cli/ballerina-cli/src/main/resources/new_cmd_defaults/Package.md new file mode 100644 index 000000000000..fc8b1b67c0a3 --- /dev/null +++ b/cli/ballerina-cli/src/main/resources/new_cmd_defaults/Package.md @@ -0,0 +1,5 @@ +Prints "Hello, World!" with a hello function. +[//]: # (above is the package summary) + +# Package Overview +Prints "Hello, World!" as the output to the command line using a hello function. diff --git a/cli/ballerina-cli/src/main/resources/new_cmd_defaults/README.md b/cli/ballerina-cli/src/main/resources/new_cmd_defaults/README.md deleted file mode 100644 index d24ddbd9f506..000000000000 --- a/cli/ballerina-cli/src/main/resources/new_cmd_defaults/README.md +++ /dev/null @@ -1,5 +0,0 @@ -Prints "Hello, World!" with a main function. - -# Overview -Provides an overview about the package when generating the API documentations. -For example, refer to https://lib.ballerina.io/ballerina/io/latest diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/BaseCommandTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/BaseCommandTest.java index e2ba186fc50f..9505cfc31587 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/BaseCommandTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/BaseCommandTest.java @@ -28,6 +28,7 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -66,23 +67,34 @@ public void beforeMethod() { this.printStream = new PrintStream(this.console); } + @DataProvider(name = "optimizeDependencyCompilation") + public Object [] [] provideOptimizeDependencyCompilation() { + return new Object [][] {{ false }, { true }}; + } + protected String readOutput() throws IOException { return readOutput(false); } protected String readOutput(boolean silent) throws IOException { + return readOutput(silent, true); + } + + protected String readOutput(boolean silent, boolean closeConsole) throws IOException { String output = ""; output = console.toString(); - console.close(); - console = new ByteArrayOutputStream(); - printStream = new PrintStream(console); + if (closeConsole) { + console.close(); + console = new ByteArrayOutputStream(); + printStream = new PrintStream(console); + } if (!silent) { PrintStream out = System.out; out.println(output); } return output; } - + /** * Execute a command and get the exception. * diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/BuildCommandTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/BuildCommandTest.java index 552065334b90..9a6fd98552aa 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/BuildCommandTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/BuildCommandTest.java @@ -93,13 +93,14 @@ public void setup() throws IOException { Files.copy(validProjectPath, this.testResources.resolve("validProject-no-permission")); } - @Test(description = "Build a valid ballerina file") - public void testBuildBalFile() throws IOException { + @Test(description = "Build a valid ballerina file", dataProvider = "optimizeDependencyCompilation") + public void testBuildBalFile(Boolean optimizeDependencyCompilation) throws IOException { Path validBalFilePath = this.testResources.resolve("valid-bal-file").resolve("hello_world.bal"); System.setProperty(USER_DIR_PROPERTY, this.testResources.resolve("valid-bal-file").toString()); // set valid source root - BuildCommand buildCommand = new BuildCommand(validBalFilePath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(validBalFilePath, printStream, printStream, false, + optimizeDependencyCompilation); // name of the file as argument new CommandLine(buildCommand).parseArgs(validBalFilePath.toString()); buildCommand.execute(); @@ -114,7 +115,8 @@ public void testBuildBalFile() throws IOException { // copying the executable to a different location before deleting // to use for testCodeGeneratorForSingleFile test case Files.copy(this.testResources.resolve("valid-bal-file").resolve("hello_world.jar"), - this.testResources.resolve("valid-bal-file").resolve("hello_world-for-codegen-test.jar")); + this.testResources.resolve("valid-bal-file").resolve("hello_world-for-codegen-test.jar"), + StandardCopyOption.REPLACE_EXISTING); Files.delete(this.testResources .resolve("valid-bal-file") @@ -226,11 +228,12 @@ public void testBuildBalFileWithNoEntry() { } } - @Test(description = "Build bal file containing syntax error") - public void testBalFileWithSyntaxError() throws IOException { + @Test(description = "Build bal file containing syntax error", dataProvider = "optimizeDependencyCompilation") + public void testBalFileWithSyntaxError(Boolean optimizeDependencyCompilation) throws IOException { // valid source root path Path balFilePath = this.testResources.resolve("bal-file-with-syntax-error").resolve("hello_world.bal"); - BuildCommand buildCommand = new BuildCommand(balFilePath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(balFilePath, printStream, printStream, false, + optimizeDependencyCompilation); // non existing bal file new CommandLine(buildCommand).parseArgs(balFilePath.toString()); try { @@ -242,11 +245,12 @@ public void testBalFileWithSyntaxError() throws IOException { } } - @Test(description = "Build bal package containing syntax error") - public void testBalProjectWithSyntaxError() throws IOException { + @Test(description = "Build bal package containing syntax error", dataProvider = "optimizeDependencyCompilation") + public void testBalProjectWithSyntaxError(Boolean optimizeDependencyCompilation) throws IOException { // valid source root path Path balFilePath = this.testResources.resolve("bal-project-with-syntax-error"); - BuildCommand buildCommand = new BuildCommand(balFilePath, printStream, printStream, false, true); + BuildCommand buildCommand = new BuildCommand(balFilePath, printStream, printStream, false, + optimizeDependencyCompilation); // non existing bal file new CommandLine(buildCommand).parseArgs(balFilePath.toString()); try { @@ -258,12 +262,12 @@ public void testBalProjectWithSyntaxError() throws IOException { } } - - @Test(description = "Build a valid ballerina project") - public void testBuildBalProject() throws IOException { + @Test(description = "Build a valid ballerina project", dataProvider = "optimizeDependencyCompilation") + public void testBuildBalProject(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("validApplicationProject"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); // non existing bal file new CommandLine(buildCommand).parseArgs(); buildCommand.execute(); @@ -281,11 +285,8 @@ public void testBuildBalProject() throws IOException { public void testCodeGeneratorForSingleFile() throws IOException { Path execPath = this.testResources.resolve("valid-bal-file").resolve("hello_world-for-codegen-test.jar"); String generatedSource = "dummyfunc-generated_1.class"; - String generatedResource = "resources/$anon/./0/openapi-spec.yaml"; - try (JarFile execJar = new JarFile(execPath.toString())) { Assert.assertNull(execJar.getJarEntry(generatedSource)); - Assert.assertNotNull(execJar.getJarEntry(generatedResource)); } } @@ -297,16 +298,11 @@ public void testCodeGeneratorForBuildProject() throws IOException { .resolve("foo-winery-0.1.0.jar"); Path execPath = projectPath.resolve("target").resolve("bin").resolve("winery.jar"); String generatedSource = "foo/winery/0/dummyfunc-generated_1.class"; - String generatedResource = "resources/foo/winery/0/openapi-spec.yaml"; - try (JarFile thinJar = new JarFile(thinJarPath.toString())) { Assert.assertNotNull(thinJar.getJarEntry(generatedSource)); - Assert.assertNotNull(thinJar.getJarEntry(generatedResource)); } - try (JarFile execJar = new JarFile(execPath.toString())) { Assert.assertNotNull(execJar.getJarEntry(generatedSource)); - Assert.assertNotNull(execJar.getJarEntry(generatedResource)); } } @@ -370,11 +366,13 @@ public void testBuildBalProjectWithJarConflicts() throws IOException { .resolve("pramodya-conflictProject-0.1.7.jar").toFile().exists()); } - @Test(description = "Build a ballerina project with provided scope platform jars") - public void testBuildProjectWithProvidedJars() { + @Test(description = "Build a ballerina project with provided scope platform jars", + dataProvider = "optimizeDependencyCompilation") + public void testBuildProjectWithProvidedJars(Boolean optimizeDependencyCompilation) { Path projectPath = this.testResources.resolve("projectWithProvidedScope"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs(); try { buildCommand.execute(); @@ -398,12 +396,13 @@ public void testBuildProjectWithProvidedWarning() throws IOException { getOutput("project-with-provided-warning.txt")); } - @Test(description = "Build a valid ballerina project with java imports") - public void testBuildJavaBalProject() throws IOException { + @Test(description = "Build a valid ballerina project with java imports", + dataProvider = "optimizeDependencyCompilation") + public void testBuildJavaBalProject(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("validJavaProject"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); - // non existing bal file + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs(); buildCommand.execute(); String buildLog = readOutput(true); @@ -416,11 +415,11 @@ public void testBuildJavaBalProject() throws IOException { .resolve("foo-winery-0.1.0.jar").toFile().exists()); } - @Test(description = "Build a valid ballerina project") - public void testBuildBalProjectFromADifferentDirectory() throws IOException { + @Test(dataProvider = "optimizeDependencyCompilation") + public void testBuildBalProjectFromADifferentDirectory(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("validApplicationProject"); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); - // non existing bal file + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs(projectPath.toString()); buildCommand.execute(); String buildLog = readOutput(true); @@ -432,12 +431,12 @@ public void testBuildBalProjectFromADifferentDirectory() throws IOException { .resolve("foo-winery-0.1.0.jar").toFile().exists()); } - @Test(description = "Build a valid ballerina project") - public void testBuildProjectWithTests() throws IOException { + @Test(dataProvider = "optimizeDependencyCompilation") + public void testBuildProjectWithTests(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("validProjectWithTests"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); - // non existing bal file + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs(); buildCommand.execute(); String buildLog = readOutput(true); @@ -449,12 +448,12 @@ public void testBuildProjectWithTests() throws IOException { .resolve("foo-winery-0.1.0.jar").toFile().exists()); } - @Test(description = "Build a valid ballerina project") - public void testBuildMultiModuleProject() throws IOException { + @Test(dataProvider = "optimizeDependencyCompilation") + public void testBuildMultiModuleProject(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("validMultiModuleProject"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); - // non existing bal file + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs(); buildCommand.execute(); String buildLog = readOutput(true); @@ -478,7 +477,6 @@ public void testBuildProjectWithDefaultBuildOptions() throws IOException { Path projectPath = this.testResources.resolve("validProjectWithBuildOptions"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); - // non existing bal file new CommandLine(buildCommand).parseArgs(); buildCommand.execute(); String buildLog = readOutput(true); @@ -499,7 +497,6 @@ public void testBuildProjectOverrideBuildOptions() throws IOException { System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); BuildCommand buildCommand = new BuildCommand( projectPath, printStream, printStream, false); - // non existing bal file new CommandLine(buildCommand).parseArgs(); try { buildCommand.execute(); @@ -530,7 +527,6 @@ public void testSingleFileWithDefaultBuildOptions() throws IOException { System.setProperty(USER_DIR_PROPERTY, this.testResources.resolve("valid-bal-file").toString()); BuildCommand buildCommand = new BuildCommand( projectPath, printStream, printStream, false); - // non existing bal file new CommandLine(buildCommand).parseArgs(); try { buildCommand.execute(); @@ -554,8 +550,7 @@ public void testSingleFileOverrideBuildOptions() throws IOException { Path projectPath = this.testResources.resolve("valid-bal-file").resolve("hello_world.bal"); System.setProperty(USER_DIR_PROPERTY, this.testResources.resolve("valid-bal-file").toString()); BuildCommand buildCommand = new BuildCommand( - projectPath, printStream, printStream, false); - // non existing bal file + projectPath, printStream, printStream, false, Boolean.TRUE); new CommandLine(buildCommand).parseArgs(); try { buildCommand.execute(); @@ -661,12 +656,13 @@ public void testBuildEmptyProjectWithBuildTools() throws IOException { getOutput("build-empty-project-with-build-tools.txt")); } - @Test(description = "Build an empty package with tests only") - public void testBuildEmptyProjectWithTestsOnly() throws IOException { + @Test(description = "Build an empty package with tests only", dataProvider = "optimizeDependencyCompilation") + public void testBuildEmptyProjectWithTestsOnly(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("emptyProjectWithTestsOnly"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs(); buildCommand.execute(); String buildLog = readOutput(true); @@ -858,8 +854,8 @@ public void testBuildEmptyPackageWithCompilerPlugin() throws IOException { } } - @Test(description = "Build a ballerina project with the flag dump-graph") - public void testBuildBalProjectWithDumpGraphFlag() throws IOException { + @Test(dataProvider = "optimizeDependencyCompilation") + public void testBuildBalProjectWithDumpGraphFlag(Boolean optimizeDependencyCompilation) throws IOException { Path dumpGraphResourcePath = this.testResources.resolve("projectsForDumpGraph"); BCompileUtil.compileAndCacheBala(dumpGraphResourcePath.resolve("package_c"), testDistCacheDirectory, projectEnvironmentBuilder); @@ -869,7 +865,8 @@ public void testBuildBalProjectWithDumpGraphFlag() throws IOException { Path projectPath = dumpGraphResourcePath.resolve("package_a"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs("--dump-graph"); buildCommand.execute(); String buildLog = readOutput(true).replace("\r", "").strip(); @@ -882,8 +879,8 @@ public void testBuildBalProjectWithDumpGraphFlag() throws IOException { ProjectUtils.deleteDirectory(projectPath.resolve("target")); } - @Test(description = "Build a ballerina project with the flag dump-raw-graphs") - public void testBuildBalProjectWithDumpRawGraphsFlag() throws IOException { + @Test(dataProvider = "optimizeDependencyCompilation") + public void testBuildBalProjectWithDumpRawGraphsFlag(Boolean optimizeDependencyCompilation) throws IOException { Path dumpGraphResourcePath = this.testResources.resolve("projectsForDumpGraph"); BCompileUtil.compileAndCacheBala(dumpGraphResourcePath.resolve("package_c"), testDistCacheDirectory, projectEnvironmentBuilder); @@ -893,7 +890,8 @@ public void testBuildBalProjectWithDumpRawGraphsFlag() throws IOException { Path projectPath = dumpGraphResourcePath.resolve("package_a"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs("--dump-raw-graphs"); buildCommand.execute(); String buildLog = readOutput(true).replace("\r", "").strip(); @@ -922,10 +920,10 @@ public void testBuildWithCorruptedDependenciesToml() throws IOException { buildLog.replaceAll("\r", ""), getOutput("corrupted-dependencies-toml.txt").replaceAll("\r", "")); String depContent = Files.readString(projectPath.resolve("Dependencies.toml"), Charset.defaultCharset()) - .replace("/r" , ""); + .replace("\r" , ""); String ballerinaShortVersion = RepoUtils.getBallerinaShortVersion(); String corrcetDepContent = Files.readString(projectPath.resolve("Dependencies-corrected.toml"), - Charset.defaultCharset()).replace("/r" , "") + Charset.defaultCharset()).replace("\r" , "") .replace("DIST_VERSION", ballerinaShortVersion); Assert.assertEquals(depContent, corrcetDepContent); Files.delete(destinationPath); @@ -973,8 +971,9 @@ public void testBirCachedProjectBuildPerformanceAfterTestCommand() { "second code gen duration is greater than the expected value"); } - @Test(description = "Build a valid ballerina project with a custom maven repo") - public void testBuildBalProjectWithCustomMavenRepo() throws IOException { + @Test(description = "Build a valid ballerina project with a custom maven repo", + dataProvider = "optimizeDependencyCompilation") + public void testBuildBalProjectWithCustomMavenRepo(Boolean optimizeDependencyCompilation) throws IOException { String username = System.getenv("publishUser"); String password = System.getenv("publishPAT"); @@ -983,7 +982,8 @@ public void testBuildBalProjectWithCustomMavenRepo() throws IOException { String content = Files.readString(projectPath.resolve("Ballerina.toml"), Charset.defaultCharset()) .replace("{{username}}", username).replace("{{password}}", password); Files.writeString(projectPath.resolve("Ballerina.toml"), content, Charset.defaultCharset()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs(projectPath.toString()); buildCommand.execute(); Assert.assertTrue(projectPath.resolve("target").resolve("platform-libs").resolve("org") @@ -1013,11 +1013,13 @@ public void publishDependencies() { testDistCacheDirectory, projectEnvironmentBuilder); } - @Test(description = "Build a new ballerina project without sticky flag", groups = {"proj-with-deps-update-policy"}) - public void testBuildNewBalProjectWithoutStickyFlag() throws IOException { + @Test(description = "Build a new ballerina project without sticky flag", groups = {"proj-with-deps-update-policy"}, + dataProvider = "optimizeDependencyCompilation") + public void testBuildNewBalProjectWithoutStickyFlag(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = testResources.resolve("dep-dist-version-projects").resolve("newPackage"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs(); buildCommand.execute(); @@ -1036,11 +1038,13 @@ public void testBuildNewBalProjectWithoutStickyFlag() throws IOException { deleteDirectory(projectPath.resolve("target")); } - @Test(description = "Build a new ballerina project with sticky flag", groups = {"proj-with-deps-update-policy"}) - public void testBuildNewBalProjectWithStickyFlag() throws IOException { + @Test(description = "Build a new ballerina project with sticky flag", groups = {"proj-with-deps-update-policy"}, + dataProvider = "optimizeDependencyCompilation") + public void testBuildNewBalProjectWithStickyFlag(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = testResources.resolve("dep-dist-version-projects").resolve("newPackage"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs("--sticky"); buildCommand.execute(); @@ -1060,12 +1064,14 @@ public void testBuildNewBalProjectWithStickyFlag() throws IOException { } @Test(description = "Build a project already built with an older distribution without sticky flag", - groups = {"proj-with-deps-update-policy"}) - public void testBuildProjectPrecompiledWithOlderDistWithoutStickyFlag() throws IOException { + groups = {"proj-with-deps-update-policy"}, dataProvider = "optimizeDependencyCompilation") + public void testBuildProjectPrecompiledWithOlderDistWithoutStickyFlag(Boolean optimizeDependencyCompilation) + throws IOException { Path projectPath = testResources.resolve("dep-dist-version-projects").resolve("preCompiledPackage"); replaceDependenciesTomlContent(projectPath, "**INSERT_DISTRIBUTION_VERSION_HERE**", "2201.5.0"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs(); buildCommand.execute(); @@ -1093,12 +1099,14 @@ public void testBuildProjectPrecompiledWithOlderDistWithoutStickyFlag() throws I } @Test(description = "Build a project already built with an older distribution with sticky flag", - groups = {"proj-with-deps-update-policy"}) - public void testBuildProjectPrecompiledWithOlderDistWithStickyFlag() throws IOException { + groups = {"proj-with-deps-update-policy"}, dataProvider = "optimizeDependencyCompilation") + public void testBuildProjectPrecompiledWithOlderDistWithStickyFlag(Boolean optimizeDependencyCompilation) + throws IOException { Path projectPath = testResources.resolve("dep-dist-version-projects").resolve("preCompiledPackage"); replaceDependenciesTomlContent(projectPath, "**INSERT_DISTRIBUTION_VERSION_HERE**", "2201.5.0"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs("--sticky"); buildCommand.execute(); @@ -1125,13 +1133,15 @@ public void testBuildProjectPrecompiledWithOlderDistWithStickyFlag() throws IOEx } @Test(description = "Build a project already built with an U4 or older distribution without sticky flag", - groups = {"proj-with-deps-update-policy"}) - public void testBuildProjectPrecompiledWithNoDistWithoutStickyFlag() throws IOException { + groups = {"proj-with-deps-update-policy"}, dataProvider = "optimizeDependencyCompilation") + public void testBuildProjectPrecompiledWithNoDistWithoutStickyFlag(Boolean optimizeDependencyCompilation) + throws IOException { Path projectPath = testResources.resolve("dep-dist-version-projects").resolve("preCompiledPackage"); replaceDependenciesTomlContent( projectPath, "distribution-version = \"**INSERT_DISTRIBUTION_VERSION_HERE**\"", ""); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false); + BuildCommand buildCommand = new BuildCommand(projectPath, printStream, printStream, false, + optimizeDependencyCompilation); new CommandLine(buildCommand).parseArgs(); buildCommand.execute(); diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/NewCommandTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/NewCommandTest.java index 80a3ebe280cc..e852274bca97 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/NewCommandTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/NewCommandTest.java @@ -568,16 +568,15 @@ public void testNewCommandWithLib(String packagePath) throws IOException { String packageName = Paths.get(args[0]).getFileName().toString(); String expectedTomlContent = "[package]\n" + - "org = \"" + System.getProperty("user.name").toLowerCase().replaceAll("[^a-z0-9_]", "_") + "\"\n" + + "org = \"" + System.getProperty("user.name").replaceAll("[^a-zA-Z0-9_]", "_") + "\"\n" + "name = \"" + packageName + "\"\n" + "version = \"0.1.0\"\n" + "distribution = \"" + RepoUtils.getBallerinaShortVersion() + "\"" + "\n"; Assert.assertTrue(tomlContent.contains(expectedTomlContent)); - Assert.assertTrue(Files.exists(packageDir.resolve(ProjectConstants.README_MD_FILE_NAME))); + Assert.assertTrue(Files.exists(packageDir.resolve(ProjectConstants.PACKAGE_MD_FILE_NAME))); Assert.assertTrue(Files.exists(packageDir.resolve(packageName + ".bal"))); Assert.assertTrue(Files.exists(packageDir.resolve(ProjectConstants.TEST_DIR_NAME))); - Assert.assertTrue(Files.exists(packageDir.resolve(ProjectConstants.RESOURCE_DIR_NAME))); Assert.assertTrue(readOutput().contains("Created new package")); } diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PackCommandTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PackCommandTest.java index 7e30bdf43e9d..83be2d4d609e 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PackCommandTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PackCommandTest.java @@ -42,8 +42,8 @@ public class PackCommandTest extends BaseCommandTest { private static final String VALID_PROJECT = "validApplicationProject"; private Path testResources; - static Path logFile = Paths.get("./src/test/resources/compiler_plugin_tests/" + - "log_creator_combined_plugin/compiler-plugin.txt"); + private static final Path logFile = Paths.get("build/logs/log_creator_combined_plugin/compiler-plugin.txt") + .toAbsolutePath(); @BeforeClass public void setup() throws IOException { @@ -68,27 +68,13 @@ public void setup() throws IOException { Files.writeString(logFile, ""); } - @Test(description = "Test package command") - public void testPackCommand() throws IOException { - Path projectPath = this.testResources.resolve(VALID_PROJECT); - System.setProperty(USER_DIR, projectPath.toString()); - - PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true); - new CommandLine(packCommand).parseArgs(); - packCommand.execute(); - String buildLog = readOutput(true); - - Assert.assertEquals(buildLog.replace("\r", ""), getOutput("compile-bal-project.txt")); - Assert.assertTrue( - projectPath.resolve("target").resolve("bala").resolve("foo-winery-any-0.1.0.bala").toFile().exists()); - } - - @Test(description = "Pack a library package") - public void testPackProject() throws IOException { + @Test(description = "Pack a library package", dataProvider = "optimizeDependencyCompilation") + public void testPackProject(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("validLibraryProject"); System.setProperty(USER_DIR, projectPath.toString()); - PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true); + PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true, + optimizeDependencyCompilation); new CommandLine(packCommand).parseArgs(); packCommand.execute(); String buildLog = readOutput(true); @@ -102,8 +88,9 @@ public void testPackProject() throws IOException { .resolve("foo-winery-0.1.0.jar").toFile().exists()); } - @Test(description = "Pack a ballerina project with the engagement of all type of compiler plugins") - public void testRunBalProjectWithAllCompilerPlugins() throws IOException { + @Test(description = "Pack a ballerina project with the engagement of all type of compiler plugins", + dataProvider = "optimizeDependencyCompilation") + public void testRunBalProjectWithAllCompilerPlugins(Boolean optimizeDependencyCompilation) throws IOException { Path compilerPluginPath = Paths.get("./src/test/resources/test-resources/compiler-plugins"); BCompileUtil.compileAndCacheBala(compilerPluginPath.resolve("log_creator_pkg_provided_code_analyzer_im") .toAbsolutePath().toString()); @@ -114,7 +101,8 @@ public void testRunBalProjectWithAllCompilerPlugins() throws IOException { Path projectPath = this.testResources.resolve("compiler-plugins").resolve("log_creator_combined_plugin"); System.setProperty(USER_DIR, projectPath.toString()); - PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true); + PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true, + optimizeDependencyCompilation); new CommandLine(packCommand).parseArgs(); packCommand.execute(); String logFileContent = Files.readString(logFile); @@ -162,18 +150,19 @@ public void testPackApplicationPackage() { public void testPackStandaloneFile() throws IOException { Path projectPath = this.testResources.resolve("valid-bal-file").resolve("hello_world.bal"); System.setProperty(USER_DIR, this.testResources.resolve("valid-bal-file").toString()); - PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true); + PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true, false); new CommandLine(packCommand).parseArgs(); packCommand.execute(); String buildLog = readOutput(true); Assert.assertTrue(buildLog.contains(" bal pack can only be used with a Ballerina package.")); } - @Test(description = "Pack a package with platform libs") - public void testPackageWithPlatformLibs() throws IOException { + @Test(description = "Pack a package with platform libs", dataProvider = "optimizeDependencyCompilation") + public void testPackageWithPlatformLibs(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("validGraalvmCompatibleProjectWithPlatformLibs"); System.setProperty(USER_DIR, projectPath.toString()); - PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true); + PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true, + optimizeDependencyCompilation); new CommandLine(packCommand).parseArgs(); packCommand.execute(); String buildLog = readOutput(true); @@ -258,11 +247,12 @@ public void testPackageWithTestOnlyJavaImports() throws IOException { .toFile().exists()); } - @Test(description = "Pack a project with a build tool execution") - public void testPackProjectWithBuildTool() throws IOException { + @Test(description = "Pack a project with a build tool execution", dataProvider = "optimizeDependencyCompilation") + public void testPackProjectWithBuildTool(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("proper-build-tool"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true); + PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true, + optimizeDependencyCompilation); new CommandLine(packCommand).parseArgs(); packCommand.execute(); String buildLog = readOutput(true); @@ -272,11 +262,13 @@ public void testPackProjectWithBuildTool() throws IOException { .toFile().exists()); } - @Test(description = "Pack a package with an empty Dependencies.toml") - public void testPackageWithEmptyDependenciesToml() throws IOException { + @Test(description = "Pack a package with an empty Dependencies.toml", + dataProvider = "optimizeDependencyCompilation") + public void testPackageWithEmptyDependenciesToml(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("validProjectWithDependenciesToml"); System.setProperty(USER_DIR, projectPath.toString()); - PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true); + PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true, + optimizeDependencyCompilation); new CommandLine(packCommand).parseArgs(); packCommand.execute(); String buildLog = readOutput(true); @@ -293,7 +285,8 @@ public void testPackageWithEmptyDependenciesToml() throws IOException { } @Test(description = "Pack a package without root package in Dependencies.toml") - public void testPackageWithoutRootPackageInDependenciesToml() throws IOException { + public void testPackageWithoutRootPackageInDependenciesToml() + throws IOException { Path projectPath = this.testResources.resolve("validProjectWoRootPkgInDepsToml"); System.setProperty(USER_DIR, projectPath.toString()); PackCommand packCommand = new PackCommand(projectPath, printStream, printStream, false, true); diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/ProfileCommandTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/ProfileCommandTest.java index a03d072748d3..0396b0af7480 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/ProfileCommandTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/ProfileCommandTest.java @@ -54,8 +54,8 @@ public class ProfileCommandTest extends BaseCommandTest { private Path testResources; - static Path logFile = Paths.get(".", "src", "test", "resources", "compiler_plugin_tests", - "log_creator_combined_plugin", "compiler-plugin.txt"); + private static final Path logFile = Paths.get("build/logs/log_creator_combined_plugin/compiler-plugin.txt") + .toAbsolutePath(); @BeforeSuite public void setupSuite() throws IOException { diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/ProjectWatcherTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/ProjectWatcherTest.java new file mode 100644 index 000000000000..2aa25fcd6806 --- /dev/null +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/ProjectWatcherTest.java @@ -0,0 +1,350 @@ +package io.ballerina.cli.cmd; + +import io.ballerina.cli.utils.ProjectWatcher; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import picocli.CommandLine; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +import static io.ballerina.cli.cmd.CommandOutputUtils.getOutput; + +/** + * Tests for the --watch flag in the run command. + * + * @since 2201.11.0 + */ +public class ProjectWatcherTest extends BaseCommandTest { + private static final String WATCH_FLAG = "--watch"; + private static final int THREAD_SLEEP_DURATION_IN_MS = 8000; + private static final String PROJECT_NAME_PLACEHOLDER = "INSERT_PROJECT_NAME"; + + private Path watchTestResources; + private Thread watcherThread; + private AtomicReference watcher; + + @BeforeClass + public void setup() throws IOException { + super.setup(); + try { + Path testResources = super.tmpDir.resolve("build-test-resources"); + this.watchTestResources = testResources.resolve("watchFlagResources"); + URI testResourcesURI = Objects.requireNonNull( + getClass().getClassLoader().getResource("test-resources")).toURI(); + Files.walkFileTree(Paths.get(testResourcesURI), + new BuildCommandTest.Copy(Paths.get(testResourcesURI), testResources)); + watcher = new AtomicReference<>(); + } catch (URISyntaxException e) { + Assert.fail("error loading resources"); + } + } + + @Test(description = "Run a correct bal service file and do a correct change") + public void testRunWatchCorrectBalFileWithCorrectChange() throws IOException, InterruptedException { + Path balFilePath = createTempFileFromTestResource("service.bal"); + RunCommand runCommand = new RunCommand(balFilePath, printStream, false); + new CommandLine(runCommand).parseArgs(WATCH_FLAG, balFilePath.toString()); + CountDownLatch latch = new CountDownLatch(1); + watcherThread = new Thread(() -> { + try { + watcher.set(new ProjectWatcher(runCommand, balFilePath, printStream)); + latch.countDown(); + watcher.get().watch(); + } catch (IOException e) { + Assert.fail("Error occurred while watching the project: " + e); + } + }); + watcherThread.start(); + latch.await(); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + replaceFileContent(balFilePath, this.watchTestResources.resolve("service-updated.bal")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + String actualOutput = readOutput(true).replace("\r", ""); + String expectedOutput = readExpectedOutputFile("watch-correct-service-file-correct-change.txt", balFilePath); + Assert.assertEquals(actualOutput, expectedOutput); + } + + @Test(description = "Run a correct bal service file and do a erroneous change") + public void testRunWatchCorrectBalFileWithErroneousChange() throws IOException, InterruptedException { + Path balFilePath = createTempFileFromTestResource("service.bal"); + RunCommand runCommand = new RunCommand(balFilePath, printStream, false); + new CommandLine(runCommand).parseArgs(WATCH_FLAG, balFilePath.toString()); + CountDownLatch latch = new CountDownLatch(1); + watcherThread = new Thread(() -> { + try { + watcher.set(new ProjectWatcher(runCommand, balFilePath, printStream)); + latch.countDown(); + watcher.get().watch(); + } catch (IOException e) { + Assert.fail("Error occurred while watching the project: " + e); + } + }); + watcherThread.start(); + latch.await(); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + replaceFileContent(balFilePath, this.watchTestResources.resolve("service-error.bal")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + String actualOutput = readOutput(true).replace("\r", ""); + String expectedOutput = readExpectedOutputFile("watch-correct-service-file-error-change.txt", balFilePath); + Assert.assertEquals(actualOutput, expectedOutput); + } + + @Test(description = "Run a erroneous bal service file and do a correct change") + public void testRunWatchErroneousBalFileWithCorrectChange() throws IOException, InterruptedException { + Path balFilePath = createTempFileFromTestResource("service-error.bal"); + RunCommand runCommand = new RunCommand(balFilePath, printStream, false); + new CommandLine(runCommand).parseArgs(WATCH_FLAG, balFilePath.toString()); + CountDownLatch latch = new CountDownLatch(1); + watcherThread = new Thread(() -> { + try { + watcher.set(new ProjectWatcher(runCommand, balFilePath, printStream)); + latch.countDown(); + watcher.get().watch(); + } catch (IOException e) { + Assert.fail("Error occurred while watching the project: " + e); + } + }); + watcherThread.start(); + latch.await(); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + replaceFileContent(balFilePath, this.watchTestResources.resolve("service.bal")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + String actualOutput = readOutput(true).replace("\r", ""); + String expectedOutput = readExpectedOutputFile("watch-error-service-file-correct-change.txt", balFilePath); + Assert.assertEquals(actualOutput, expectedOutput); + } + + @Test(description = "Run a bal file with no service") + public void testRunWatchBalFileWithNoService() throws IOException, InterruptedException { + Path balFilePath = createTempFileFromTestResource("main.bal"); + RunCommand runCommand = new RunCommand(balFilePath, printStream, false); + new CommandLine(runCommand).parseArgs(WATCH_FLAG, balFilePath.toString()); + CountDownLatch latch = new CountDownLatch(1); + watcherThread = new Thread(() -> { + try { + watcher.set(new ProjectWatcher(runCommand, balFilePath, printStream)); + latch.countDown(); + watcher.get().watch(); + } catch (IOException e) { + Assert.fail("Error occurred while watching the project: " + e); + } + }); + watcherThread.start(); + latch.await(); + watcherThread.join(THREAD_SLEEP_DURATION_IN_MS); + Assert.assertFalse(watcherThread.isAlive()); + String actualOutput = readOutput(true).replace("\r", ""); + String expectedOutput = readExpectedOutputFile("watch-no-service-file.txt", balFilePath); + Assert.assertEquals(actualOutput, expectedOutput); + } + + @Test(description = "Run a bal service project and do a correct change") + public void testRunWatchBalProjectWithCorrectChange() throws IOException, InterruptedException { + Path balProjectPath = createTempDirFromTestResource("service"); + RunCommand runCommand = new RunCommand(balProjectPath, printStream, false); + new CommandLine(runCommand).parseArgs(WATCH_FLAG, balProjectPath.toString()); + CountDownLatch latch = new CountDownLatch(1); + watcherThread = new Thread(() -> { + try { + watcher.set(new ProjectWatcher(runCommand, balProjectPath, printStream)); + latch.countDown(); + watcher.get().watch(); + } catch (IOException e) { + Assert.fail("Error occurred while watching the project: " + e); + } + }); + watcherThread.start(); + latch.await(); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + // Update a source file + replaceFileContent(balProjectPath.resolve("service.bal"), + this.watchTestResources.resolve("project-service-updated.bal")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + // Add a new source file + Files.copy(this.watchTestResources.resolve("constants.bal"), balProjectPath.resolve("constants.bal")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + // Remove a source file + Files.delete(balProjectPath.resolve("constants.bal")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + String actualOutput = readOutput(true).replace("\r", ""); + String expectedOutput = readExpectedOutputFile("watch-correct-service-project-correct-change.txt", + balProjectPath); + Assert.assertEquals(actualOutput, expectedOutput); + } + + @Test(description = "Run a bal project with no service") + public void testRunWatchBalProjectWithNoService() throws IOException, InterruptedException { + Path balProjectPath = createTempDirFromTestResource("main"); + RunCommand runCommand = new RunCommand(balProjectPath, printStream, false); + new CommandLine(runCommand).parseArgs(WATCH_FLAG, balProjectPath.toString()); + CountDownLatch latch = new CountDownLatch(1); + watcherThread = new Thread(() -> { + try { + watcher.set(new ProjectWatcher(runCommand, balProjectPath, printStream)); + latch.countDown(); + watcher.get().watch(); + } catch (IOException e) { + Assert.fail("Error occurred while watching the project: " + e); + } + }); + watcherThread.start(); + latch.await(); + watcherThread.join(THREAD_SLEEP_DURATION_IN_MS); + Assert.assertFalse(watcherThread.isAlive()); + String actualOutput = readOutput(true).replace("\r", ""); + String expectedOutput = readExpectedOutputFile("watch-no-service-project.txt", balProjectPath); + Assert.assertEquals(actualOutput, expectedOutput); + } + + @Test(description = "Run a bal service project and do valid file change") + public void testRunWatchBalProjectWithValidFileChanges() throws IOException, InterruptedException { + Path balProjectPath = createTempDirFromTestResource("service"); + RunCommand runCommand = new RunCommand(balProjectPath, printStream, false); + new CommandLine(runCommand).parseArgs(WATCH_FLAG, balProjectPath.toString()); + CountDownLatch latch = new CountDownLatch(1); + watcherThread = new Thread(() -> { + try { + watcher.set(new ProjectWatcher(runCommand, balProjectPath, printStream)); + latch.countDown(); + watcher.get().watch(); + } catch (IOException e) { + Assert.fail("Error occurred while watching the project: " + e); + } + }); + watcherThread.start(); + latch.await(); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + // Update the Ballerina.toml + replaceFileContent(balProjectPath.resolve("Ballerina.toml"), + this.watchTestResources.resolve("Ballerina-copy.toml")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + // Add a new module with a source file + Files.copy(this.watchTestResources.resolve("mod1.bal"), + balProjectPath.resolve("modules").resolve("mod1").resolve("mod1.bal")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + // Add a new resource file + Files.copy(this.watchTestResources.resolve("hello.txt"), + balProjectPath.resolve("resources").resolve("hello.txt")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + String actualOutput = readOutput(true).replace("\r", ""); + String expectedOutput = readExpectedOutputFile("watch-service-project-valid-changes.txt", balProjectPath); + Assert.assertEquals(actualOutput, expectedOutput); + } + + @Test(description = "Run a bal service project and do invalid file change") + public void testRunWatchBalProjectWithInvalidFileChanges() throws IOException, InterruptedException { + Path balProjectPath = createTempDirFromTestResource("service"); + RunCommand runCommand = new RunCommand(balProjectPath, printStream, false); + new CommandLine(runCommand).parseArgs(WATCH_FLAG, balProjectPath.toString()); + CountDownLatch latch = new CountDownLatch(1); + watcherThread = new Thread(() -> { + try { + watcher.set(new ProjectWatcher(runCommand, balProjectPath, printStream)); + latch.countDown(); + watcher.get().watch(); + } catch (IOException e) { + Assert.fail("Error occurred while watching the project: " + e); + } + }); + watcherThread.start(); + latch.await(); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + // Add test file + Path testFilePath = balProjectPath.resolve("tests").resolve("test.bal"); + Files.copy(this.watchTestResources.resolve("constants.bal"), testFilePath); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + // Change test file + replaceFileContent(testFilePath, this.watchTestResources.resolve("project-service-updated.bal")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + // Delete a test file + Files.delete(testFilePath); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + // Add a test file to a module + Files.copy(this.watchTestResources.resolve("constants.bal"), + balProjectPath.resolve("modules").resolve("mod1").resolve("tests").resolve("test.bal")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + // Add a source file into modules/ + Files.copy(this.watchTestResources.resolve("constants.bal"), + balProjectPath.resolve("modules").resolve("constants.bal")); + + // Add a json file to the root + Files.copy(this.watchTestResources.resolve("hello.json"), balProjectPath.resolve("hello.json")); + + // Add a source file to target/ + Files.copy(this.watchTestResources.resolve("constants.bal"), + balProjectPath.resolve("target").resolve("constants.bal")); + Thread.sleep(THREAD_SLEEP_DURATION_IN_MS); + + String actualOutput = readOutput(true).replace("\r", ""); + String expectedOutput = readExpectedOutputFile("watch-service-project-invalid-changes.txt", balProjectPath); + Assert.assertEquals(actualOutput, expectedOutput); + } + + @AfterMethod + public void afterMethod() { + try { + if (watcherThread != null && watcher.get() != null) { + stopProjectWatcher(watcherThread, watcher.get()); + } + } catch (InterruptedException e) { + Assert.fail("Error occurred while stopping the project watcher. " + + "Please kill any stale java processes that were started by the project watcher tests: " + e); + } + } + + private Path createTempFileFromTestResource(String fileName) throws IOException { + Path balFilePath = this.watchTestResources.resolve(fileName); + Path tempFilePath = Files.createTempFile("service", ".bal"); + replaceFileContent(tempFilePath, balFilePath); + return tempFilePath; + } + + private Path createTempDirFromTestResource(String projectName) throws IOException { + Path balProjectPath = this.watchTestResources.resolve(projectName); + Path tempProjectPath = Files.createTempDirectory("service"); + tempProjectPath.toFile().deleteOnExit(); + Files.walkFileTree(balProjectPath, new BuildCommandTest.Copy(balProjectPath, tempProjectPath)); + return tempProjectPath; + } + + private void replaceFileContent(Path filePath, Path copyFrom) { + try { + String newContent = Files.readString(copyFrom); + Files.writeString(filePath, newContent); + } catch (IOException e) { + Assert.fail("Error occurred while writing to the file: " + e); + } + } + + private String readExpectedOutputFile(String expectedOutputFile, Path balFilePath) throws IOException { + return getOutput(expectedOutputFile) + .replace(PROJECT_NAME_PLACEHOLDER, balFilePath.getFileName().toString()); + } + + private void stopProjectWatcher(Thread thread, ProjectWatcher watcher) throws InterruptedException { + watcher.stopWatching(); + thread.join(); + } +} diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PullCommandTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PullCommandTest.java index 5394a9f3d17e..c2cc362f11f8 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PullCommandTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PullCommandTest.java @@ -76,7 +76,7 @@ public void testPullInvalidPackage() throws IOException { String buildLog = readOutput(true); String actual = buildLog.replaceAll("\r", ""); Assert.assertTrue( - actual.contains("ballerina: invalid package name. Provide the package name with the organization.")); + actual.contains("ballerina: invalid package. Provide the package name with the organization.")); Assert.assertTrue( actual.contains("bal pull {/ | /:}")); } diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PushCommandTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PushCommandTest.java index 89d6333059c4..f3cf82e0b936 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PushCommandTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/PushCommandTest.java @@ -383,7 +383,7 @@ public void testPushWithoutPackageMd() throws IOException { projectPath.resolve("target").resolve("bala").resolve("foo-winery-any-0.1.0.bala").toFile().exists()); // Push - String expected = "README.md is missing in bala file"; + String expected = "Package.md is missing in bala file"; PushCommand pushCommand = new PushCommand(projectPath, printStream, printStream, false); new CommandLine(pushCommand).parseArgs(); diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunBuildToolsTaskTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunBuildToolsTaskTest.java index 5b2a1fc7820a..ecfd5eb8cb6e 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunBuildToolsTaskTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunBuildToolsTaskTest.java @@ -58,9 +58,9 @@ public class RunBuildToolsTaskTest extends BaseCommandTest { private static final long TWO_DAYS = 2 * 24 * 60 * 60 * 1000; private static final long HALF_DAY = 12 * 60 * 60 * 1000; - - private static final Path LOG_FILE = Paths.get("./src/test/resources/compiler_plugin_tests/" + - "log_creator_combined_plugin/compiler-plugin.txt"); + + private static final Path LOG_FILE = Paths.get("build/logs/log_creator_combined_plugin/compiler-plugin.txt") + .toAbsolutePath(); @BeforeClass public void setup() throws IOException { diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunCommandTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunCommandTest.java index 44e724054248..8c5f2043aa17 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunCommandTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunCommandTest.java @@ -44,8 +44,7 @@ public class RunCommandTest extends BaseCommandTest { private Path testResources; private Path testDistCacheDirectory; private ProjectEnvironmentBuilder projectEnvironmentBuilder; - static Path logFile = Paths.get("./src/test/resources/compiler_plugin_tests/" + - "log_creator_combined_plugin/compiler-plugin.txt"); + static Path logFile = Paths.get("build/logs/log_creator_combined_plugin/compiler-plugin.txt").toAbsolutePath(); @BeforeSuite public void setupSuite() throws IOException { @@ -72,14 +71,14 @@ public void setup() throws IOException { } } - @Test(description = "Run a valid ballerina file") - public void testRunValidBalFile() throws IOException { + @Test(description = "Run a valid ballerina file", dataProvider = "optimizeDependencyCompilation") + public void testRunValidBalFile(Boolean optimizeDependencyCompilation) throws IOException { Path validBalFilePath = this.testResources.resolve("valid-run-bal-file").resolve("file_create.bal"); System.setProperty("user.dir", this.testResources.resolve("valid-run-bal-file").toString()); Path tempFile = this.testResources.resolve("valid-run-bal-file").resolve("temp.txt"); // set valid source root - RunCommand runCommand = new RunCommand(validBalFilePath, printStream, false); + RunCommand runCommand = new RunCommand(validBalFilePath, printStream, false, optimizeDependencyCompilation); // name of the file as argument new CommandLine(runCommand).setEndOfOptionsDelimiter("").setUnmatchedOptionsArePositionalParams(true) .parseArgs(validBalFilePath.toString(), "--", tempFile.toString()); @@ -100,13 +99,11 @@ public void testRunNonExistingBalFile() throws IOException { // valid source root path Path validBalFilePath = this.testResources.resolve("valid-run-bal-file").resolve("xyz.bal"); RunCommand runCommand = new RunCommand(validBalFilePath, printStream, false); - // non existing bal file new CommandLine(runCommand).parseArgs(validBalFilePath.toString()); runCommand.execute(); String buildLog = readOutput(true); Assert.assertTrue(buildLog.replace("\r", "") .contains("The file does not exist: " + validBalFilePath)); - } @Test(description = "Run bal file containing syntax error") @@ -114,7 +111,6 @@ public void testRunBalFileWithSyntaxError() { // valid source root path Path balFilePath = this.testResources.resolve("bal-file-with-syntax-error").resolve("hello_world.bal"); RunCommand runCommand = new RunCommand(balFilePath, printStream, false); - // non existing bal file new CommandLine(runCommand).parseArgs(balFilePath.toString()); try { runCommand.execute(); @@ -128,7 +124,6 @@ public void testRunBalProjectWithSyntaxError() { // valid source root path Path balFilePath = this.testResources.resolve("bal-project-with-syntax-error"); RunCommand runCommand = new RunCommand(balFilePath, printStream, false); - // non existing bal file new CommandLine(runCommand).parseArgs(balFilePath.toString()); try { runCommand.execute(); @@ -155,14 +150,15 @@ public void testRunValidBalProject() throws IOException { Files.delete(tempFile); } - @Test(description = "Run a valid ballerina project from the project directory") - public void testRunValidBalProjectFromProjectDir() throws IOException { + @Test(description = "Run a valid ballerina project from the project directory", + dataProvider = "optimizeDependencyCompilation") + public void testRunValidBalProjectFromProjectDir(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("validRunProject"); Path tempFile = projectPath.resolve("temp.txt"); System.setProperty("user.dir", this.testResources.resolve("validRunProject").toString()); // set valid source root - RunCommand runCommand = new RunCommand(projectPath, printStream, false); + RunCommand runCommand = new RunCommand(projectPath, printStream, false, optimizeDependencyCompilation); // name of the file as argument new CommandLine(runCommand).setEndOfOptionsDelimiter("").setUnmatchedOptionsArePositionalParams(true) .parseArgs("--", tempFile.toString()); @@ -174,11 +170,11 @@ public void testRunValidBalProjectFromProjectDir() throws IOException { Files.delete(tempFile); } - @Test(description = "Run a project with a build tool execution") - public void testRunProjectWithBuildTool() throws IOException { + @Test(description = "Run a project with a build tool execution", dataProvider = "optimizeDependencyCompilation") + public void testRunProjectWithBuildTool(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("proper-build-tool"); System.setProperty(USER_DIR_PROPERTY, projectPath.toString()); - RunCommand runCommand = new RunCommand(projectPath, printStream, false); + RunCommand runCommand = new RunCommand(projectPath, printStream, false, optimizeDependencyCompilation); new CommandLine(runCommand).parseArgs(); runCommand.execute(); String buildLog = readOutput(true); @@ -204,11 +200,12 @@ public void testRunCommandWithInvalidArg() { } } - @Test(description = "Run a valid ballerina file that has an import having platform libs") - public void testRunProjectContainingImportsWithPlatformLibs() { + @Test(description = "Run a valid ballerina file that has an import having platform libs", + dataProvider = "optimizeDependencyCompilation") + public void testRunProjectContainingImportsWithPlatformLibs(Boolean optimizeDependencyCompilation) { Path projectPath = this.testResources.resolve("validRunProjectImportsWithPlatformLibs"); // set valid source root - RunCommand runCommand = new RunCommand(projectPath, printStream, false); + RunCommand runCommand = new RunCommand(projectPath, printStream, false, optimizeDependencyCompilation); // name of the file as argument new CommandLine(runCommand).parseArgs(projectPath.toString()); @@ -296,10 +293,10 @@ public void testRunWithCustomTarget() { } } - @Test(description = "Run a ballerina project with the engagement of all type of compiler plugins") - public void testRunBalProjectWithAllCompilerPlugins() throws IOException { - Path logFile = Paths.get("./src/test/resources/compiler_plugin_tests/" + - "log_creator_combined_plugin/compiler-plugin.txt"); + @Test(description = "Run a ballerina project with the engagement of all type of compiler plugins", + dataProvider = "optimizeDependencyCompilation") + public void testRunBalProjectWithAllCompilerPlugins(Boolean optimizeDependencyCompilation) throws IOException { + Path logFile = Paths.get("build/logs/log_creator_combined_plugin/compiler-plugin.txt").toAbsolutePath(); Files.createDirectories(logFile.getParent()); Files.writeString(logFile, ""); Path compilerPluginPath = Paths.get("./src/test/resources/test-resources").resolve("compiler-plugins"); @@ -312,7 +309,7 @@ public void testRunBalProjectWithAllCompilerPlugins() throws IOException { Path projectPath = this.testResources.resolve("compiler-plugins").resolve("log_creator_combined_plugin"); System.setProperty("user.dir", projectPath.toString()); - RunCommand runCommand = new RunCommand(projectPath, printStream, false); + RunCommand runCommand = new RunCommand(projectPath, printStream, false, optimizeDependencyCompilation); new CommandLine(runCommand).parseArgs(); runCommand.execute(); String logFileContent = Files.readString(logFile); diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/TestCommandTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/TestCommandTest.java index 31dfee5177b1..26ff683c1d4c 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/TestCommandTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/TestCommandTest.java @@ -99,13 +99,13 @@ public void setup() throws IOException { } } - @Test(description = "Test a valid ballerina file") - public void testTestBalFile() { + @Test(description = "Test a valid ballerina file", dataProvider = "optimizeDependencyCompilation") + public void testTestBalFile(Boolean optimizeDependencyCompilation) { Path validBalFilePath = this.testResources.resolve("valid-test-bal-file").resolve("sample_tests.bal"); System.setProperty(ProjectConstants.USER_DIR, this.testResources.resolve("valid-test-bal-file").toString()); // set valid source root - TestCommand testCommand = new TestCommand(validBalFilePath, false); + TestCommand testCommand = new TestCommand(validBalFilePath, false, optimizeDependencyCompilation); // name of the file as argument new CommandLine(testCommand).parseArgs(validBalFilePath.toString()); testCommand.execute(); @@ -140,7 +140,6 @@ public void testNonExistingBalFile() throws IOException { // valid source root path Path validBalFilePath = this.testResources.resolve("valid-non-bal-file").resolve("xyz.bal"); TestCommand testCommand = new TestCommand(validBalFilePath, printStream, printStream, false); - // non existing bal file new CommandLine(testCommand).parseArgs(validBalFilePath.toString()); testCommand.execute(); String buildLog = readOutput(true); @@ -154,7 +153,6 @@ public void testBalFileWithSyntaxError() { // valid source root path Path balFilePath = this.testResources.resolve("bal-file-with-syntax-error").resolve("sample_tests.bal"); TestCommand testCommand = new TestCommand(balFilePath, printStream, printStream, false); - // non existing bal file new CommandLine(testCommand).parseArgs(balFilePath.toString()); try { testCommand.execute(); @@ -168,7 +166,6 @@ public void testBuildProjectWithTests() throws IOException { Path projectPath = this.testResources.resolve("validProjectWithTests"); System.setProperty(ProjectConstants.USER_DIR, projectPath.toString()); TestCommand testCommand = new TestCommand(projectPath, printStream, printStream, false); - // non existing bal file new CommandLine(testCommand).parseArgs(); testCommand.execute(); String buildLog = readOutput(true); @@ -180,7 +177,6 @@ public void testBuildMultiModuleProject() { Path projectPath = this.testResources.resolve("validMultiModuleProjectWithTests"); System.setProperty(ProjectConstants.USER_DIR, projectPath.toString()); TestCommand testCommand = new TestCommand(projectPath, printStream, printStream, false); - // non existing bal file new CommandLine(testCommand).parseArgs(); testCommand.execute(); } @@ -189,7 +185,6 @@ public void testBuildMultiModuleProject() { public void testTestBalProjectFromADifferentDirectory() throws IOException { Path projectPath = this.testResources.resolve("validProjectWithTests"); TestCommand buildCommand = new TestCommand(projectPath, printStream, printStream, false); - // non existing bal file new CommandLine(buildCommand).parseArgs(projectPath.toString()); buildCommand.execute(); String buildLog = readOutput(true); @@ -251,11 +246,13 @@ public void testTestCommandPreservingBinJarInTargetDir() throws IOException { .resolve("foo-winery-0.1.0-testable.jar").toFile().exists()); } - @Test(description = "Test a ballerina project with an invalid argument for --coverage-format") - public void testUnsupportedCoverageFormat() throws IOException { + @Test(description = "Test a ballerina project with an invalid argument for --coverage-format", + dataProvider = "optimizeDependencyCompilation") + public void testUnsupportedCoverageFormat(Boolean optimizeDependencyCompilation) throws IOException { Path projectPath = this.testResources.resolve("validProjectWithTests"); TestCommand testCommand = new TestCommand( - projectPath, printStream, printStream, false, false, true, "html"); + projectPath, printStream, printStream, false, false, true, "html", + optimizeDependencyCompilation); new CommandLine(testCommand).parseArgs(); testCommand.execute(); @@ -288,11 +285,11 @@ public void testCustomTargetDirWithTestCmd() { ".json"))); } - @Test(description = "Test a ballerina project with --test-report") - public void testTestWithReport() { + @Test(description = "Test a ballerina project with --test-report", dataProvider = "optimizeDependencyCompilation") + public void testTestWithReport(Boolean optimizeDependencyCompilation) { Path projectPath = this.testResources.resolve("validProjectWithTests"); TestCommand testCommand = new TestCommand( - projectPath, printStream, printStream, false, true, false, null); + projectPath, printStream, printStream, false, true, false, null, optimizeDependencyCompilation); new CommandLine(testCommand).parseArgs(); try (MockedStatic testUtilsMockedStatic = Mockito.mockStatic( TestUtils.class, Mockito.CALLS_REAL_METHODS)) { diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/TestNativeImageCommandTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/TestNativeImageCommandTest.java index aaa5cc24e1b9..bf703fc6de89 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/TestNativeImageCommandTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/TestNativeImageCommandTest.java @@ -30,8 +30,8 @@ public class TestNativeImageCommandTest extends BaseCommandTest { private Path testResources; private Path testDistCacheDirectory; ProjectEnvironmentBuilder projectEnvironmentBuilder; - private static final Path LOG_FILE = Paths.get("./src/test/resources/compiler_plugin_tests/" + - "log_creator_combined_plugin/compiler-plugin.txt"); + private static final Path LOG_FILE = Paths.get("build/logs/log_creator_combined_plugin/compiler-plugin.txt") + .toAbsolutePath(); @BeforeClass public void setup() throws IOException { diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/pack-central-tool.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/pack-central-tool.txt index af14be8d44b0..c52dd2cde0ca 100644 --- a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/pack-central-tool.txt +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/pack-central-tool.txt @@ -1,5 +1,3 @@ -WARNING [sample_tool_template] The use of Package.md and Module.md is deprecated. Update the package to add a README.md file. - Compiling source testorg/sample_tool_template:1.0.0 diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-correct-service-file-correct-change.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-correct-service-file-correct-change.txt new file mode 100644 index 000000000000..ae3a1ee38286 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-correct-service-file-correct-change.txt @@ -0,0 +1,14 @@ +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-correct-service-file-error-change.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-correct-service-file-error-change.txt new file mode 100644 index 000000000000..06b67c871dd7 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-correct-service-file-error-change.txt @@ -0,0 +1,13 @@ +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files +ERROR [INSERT_PROJECT_NAME:(17:1,17:1)] missing semicolon token +error: compilation contains errors diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-correct-service-project-correct-change.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-correct-service-project-correct-change.txt new file mode 100644 index 000000000000..77dcb5d68682 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-correct-service-project-correct-change.txt @@ -0,0 +1,26 @@ +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-error-service-file-correct-change.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-error-service-file-correct-change.txt new file mode 100644 index 000000000000..6c9d0b1b8de9 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-error-service-file-correct-change.txt @@ -0,0 +1,13 @@ +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files +ERROR [INSERT_PROJECT_NAME:(17:1,17:1)] missing semicolon token +error: compilation contains errors + +Detected file changes. Re-running the project... +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-no-service-file.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-no-service-file.txt new file mode 100644 index 000000000000..903d4cde4f1e --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-no-service-file.txt @@ -0,0 +1,7 @@ +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files +WARNING [INSERT_PROJECT_NAME:(3:5,3:19)] unused variable 'y' + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-no-service-project.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-no-service-project.txt new file mode 100644 index 000000000000..0661f8043992 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-no-service-project.txt @@ -0,0 +1,5 @@ +Compiling source + gayaldassanayake/main:0.1.0 + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-service-project-invalid-changes.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-service-project-invalid-changes.txt new file mode 100644 index 000000000000..74b3a0160cfb --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-service-project-invalid-changes.txt @@ -0,0 +1,5 @@ +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-service-project-valid-changes.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-service-project-valid-changes.txt new file mode 100644 index 000000000000..fbcb07f92993 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/unix/watch-service-project-valid-changes.txt @@ -0,0 +1,26 @@ +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.1 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.1 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.1 + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/corrupted-dependencies-toml.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/corrupted-dependencies-toml.txt index 33f997a713eb..79554d36dd69 100644 --- a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/corrupted-dependencies-toml.txt +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/corrupted-dependencies-toml.txt @@ -3,4 +3,4 @@ Compiling source WARNING [Dependencies.toml:(6:1,18:1)] Detected corrupted Dependencies.toml file. Dependencies will be updated to the latest versions. Generating executable - target/bin/test1.jar + target\bin\test1.jar diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/pack-central-tool.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/pack-central-tool.txt index 023a3175fe59..9bd1ea7d6277 100644 --- a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/pack-central-tool.txt +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/pack-central-tool.txt @@ -1,7 +1,5 @@ -WARNING [sample_tool_template] The use of Package.md and Module.md is deprecated. Update the package to add a README.md file. - -Compiling source - testorg/sample_tool_template:1.0.0 - -Creating bala - target\bala\testorg-sample_tool_template-java17-1.0.0.bala +Compiling source + testorg/sample_tool_template:1.0.0 + +Creating bala + target\bala\testorg-sample_tool_template-java17-1.0.0.bala diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-correct-service-file-correct-change.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-correct-service-file-correct-change.txt new file mode 100644 index 000000000000..ae3a1ee38286 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-correct-service-file-correct-change.txt @@ -0,0 +1,14 @@ +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-correct-service-file-error-change.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-correct-service-file-error-change.txt new file mode 100644 index 000000000000..06b67c871dd7 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-correct-service-file-error-change.txt @@ -0,0 +1,13 @@ +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files +ERROR [INSERT_PROJECT_NAME:(17:1,17:1)] missing semicolon token +error: compilation contains errors diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-correct-service-project-correct-change.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-correct-service-project-correct-change.txt new file mode 100644 index 000000000000..77dcb5d68682 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-correct-service-project-correct-change.txt @@ -0,0 +1,26 @@ +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-error-service-file-correct-change.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-error-service-file-correct-change.txt new file mode 100644 index 000000000000..6c9d0b1b8de9 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-error-service-file-correct-change.txt @@ -0,0 +1,13 @@ +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files +ERROR [INSERT_PROJECT_NAME:(17:1,17:1)] missing semicolon token +error: compilation contains errors + +Detected file changes. Re-running the project... +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-no-service-file.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-no-service-file.txt new file mode 100644 index 000000000000..903d4cde4f1e --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-no-service-file.txt @@ -0,0 +1,7 @@ +Compiling source + INSERT_PROJECT_NAME +WARNING [:(1:1,1:1)] Skipped adding the generated source file with prefix "dummyfunc". Source file generation is not supported with standalone bal files +WARNING [INSERT_PROJECT_NAME:(3:5,3:19)] unused variable 'y' + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-no-service-project.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-no-service-project.txt new file mode 100644 index 000000000000..0661f8043992 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-no-service-project.txt @@ -0,0 +1,5 @@ +Compiling source + gayaldassanayake/main:0.1.0 + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-service-project-invalid-changes.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-service-project-invalid-changes.txt new file mode 100644 index 000000000000..74b3a0160cfb --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-service-project-invalid-changes.txt @@ -0,0 +1,5 @@ +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-service-project-valid-changes.txt b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-service-project-valid-changes.txt new file mode 100644 index 000000000000..fbcb07f92993 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/command-outputs/windows/watch-service-project-valid-changes.txt @@ -0,0 +1,26 @@ +Compiling source + gayaldassanayake/service:0.1.0 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.1 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.1 + +Running executable + + +Detected file changes. Re-running the project... +Compiling source + gayaldassanayake/service:0.1.1 + +Running executable + diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/Ballerina-copy.toml b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/Ballerina-copy.toml new file mode 100644 index 000000000000..a91aeeec13da --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/Ballerina-copy.toml @@ -0,0 +1,5 @@ +[package] +org = "gayaldassanayake" +name = "service" +version = "0.1.1" +distribution = "2201.9.2" diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/constants.bal b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/constants.bal new file mode 100644 index 000000000000..b7aba66f8026 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/constants.bal @@ -0,0 +1 @@ +const a = "a"; \ No newline at end of file diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/hello.json b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/hello.json new file mode 100644 index 000000000000..d1ed97aa9cde --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/hello.json @@ -0,0 +1,4 @@ +{ + "a": "hello", + "b": "world" +} \ No newline at end of file diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/hello.txt b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/hello.txt new file mode 100644 index 000000000000..897fa26153b8 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/hello.txt @@ -0,0 +1,2 @@ +Hello, +World diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/main.bal b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/main.bal new file mode 100644 index 000000000000..6e8cbcecdc44 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/main.bal @@ -0,0 +1,4 @@ +public function main() { + int x = 5; + int y = x + 2; +} diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/main/Ballerina.toml b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/main/Ballerina.toml new file mode 100644 index 000000000000..83e591cc3172 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/main/Ballerina.toml @@ -0,0 +1,5 @@ +[package] +org = "gayaldassanayake" +name = "main" +version = "0.1.0" +distribution = "2201.9.2" diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/main/service.bal b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/main/service.bal new file mode 100644 index 000000000000..6b71ef415c69 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/main/service.bal @@ -0,0 +1,2 @@ +public function main() { +} diff --git a/cli/ballerina-cli/src/main/resources/create_cmd_templates/lib/resources/.keep b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/mod1.bal similarity index 100% rename from cli/ballerina-cli/src/main/resources/create_cmd_templates/lib/resources/.keep rename to cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/mod1.bal diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/project-service-updated.bal b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/project-service-updated.bal new file mode 100644 index 000000000000..d2e27c56f745 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/project-service-updated.bal @@ -0,0 +1,37 @@ +public function main() {} + +public class Listener { + int port; + public function 'start() returns error? {} + + public function gracefulStop() returns error? {} + + public function immediateStop() returns error? {} + + public function detach(service object {} s) returns error? {} + + public function attach(service object {} s, string[]? name = ()) returns error? {} + + public function init(int port) { + self.port = port; + } +} + +listener Listener 'listener = new Listener(9000); + +service / on 'listener { + function init() { + } + + isolated resource function get joke() returns string { + return getJoke(); + } + + isolated resource function post joke(string joke) { + setJoke(joke); + } + + isolated resource function get jokes() returns string[] { + return []; + } +} diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service-error.bal b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service-error.bal new file mode 100644 index 000000000000..a2cce38999ab --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service-error.bal @@ -0,0 +1,24 @@ +public function main() {} + +public class Listener { + int port; + public function 'start() returns error? {} + + public function gracefulStop() returns error? {} + + public function immediateStop() returns error? {} + + public function detach(service object {} s) returns error? {} + + public function attach(service object {} s, string[]? name = ()) returns error? {} + + public function init(int port) { + self.port = port // Missing colon here + } +} + +listener Listener 'listener = new Listener(9000); + +service / on 'listener { + function init() {} +} diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service-updated.bal b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service-updated.bal new file mode 100644 index 000000000000..bf2557603187 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service-updated.bal @@ -0,0 +1,24 @@ +public function main() {} + +public class Listener { + int port; + public function 'start() returns error? {} + + public function gracefulStop() returns error? {} + + public function immediateStop() returns error? {} + + public function detach(service object {} s) returns error? {} + + public function attach(service object {} s, string[]? name = ()) returns error? {} + + public function init(int port) { + self.port = port; + } +} + +listener Listener 'listener = new Listener(9090); + +service / on 'listener { + function init() {} +} diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service.bal b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service.bal new file mode 100644 index 000000000000..d2f9de9acec6 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service.bal @@ -0,0 +1,24 @@ +public function main() {} + +public class Listener { + int port; + public function 'start() returns error? {} + + public function gracefulStop() returns error? {} + + public function immediateStop() returns error? {} + + public function detach(service object {} s) returns error? {} + + public function attach(service object {} s, string[]? name = ()) returns error? {} + + public function init(int port) { + self.port = port; + } +} + +listener Listener 'listener = new Listener(9000); + +service / on 'listener { + function init() {} +} diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/Ballerina.toml b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/Ballerina.toml new file mode 100644 index 000000000000..4749b4e3d8c5 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/Ballerina.toml @@ -0,0 +1,5 @@ +[package] +org = "gayaldassanayake" +name = "service" +version = "0.1.0" +distribution = "2201.9.2" diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/modules/mod1/tests/.gitkeep b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/modules/mod1/tests/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/resources/.gitkeep b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/resources/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/service.bal b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/service.bal new file mode 100644 index 000000000000..66f1b01068f2 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/service.bal @@ -0,0 +1,33 @@ +public function main() {} + +public class Listener { + int port; + public function 'start() returns error? {} + + public function gracefulStop() returns error? {} + + public function immediateStop() returns error? {} + + public function detach(service object {} s) returns error? {} + + public function attach(service object {} s, string[]? name = ()) returns error? {} + + public function init(int port) { + self.port = port; + } +} + +listener Listener 'listener = new Listener(9000); + +service / on 'listener { + function init() { + } + + isolated resource function get joke() returns string { + return getJoke(); + } + + isolated resource function post joke(string joke) { + setJoke(joke); + } +} diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/tests/.gitkeep b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/tests/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/utils.bal b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/utils.bal new file mode 100644 index 000000000000..cb1745fcba67 --- /dev/null +++ b/cli/ballerina-cli/src/test/resources/test-resources/watchFlagResources/service/utils.bal @@ -0,0 +1,6 @@ +isolated function getJoke() returns string { + return "Chuck Norris breathes air ... five times a day."; +} + +isolated function setJoke(string joke) { +} diff --git a/cli/ballerina-cli/src/test/resources/testng.xml b/cli/ballerina-cli/src/test/resources/testng.xml index 2b477f334530..8a68cc310ad4 100644 --- a/cli/ballerina-cli/src/test/resources/testng.xml +++ b/cli/ballerina-cli/src/test/resources/testng.xml @@ -45,6 +45,7 @@ under the License. + diff --git a/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralAPIClient.java b/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralAPIClient.java index 711cc646faa3..c1a505cf5efa 100644 --- a/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralAPIClient.java +++ b/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralAPIClient.java @@ -83,6 +83,7 @@ import static org.ballerinalang.central.client.CentralClientConstants.APPLICATION_OCTET_STREAM; import static org.ballerinalang.central.client.CentralClientConstants.AUTHORIZATION; import static org.ballerinalang.central.client.CentralClientConstants.BALA_URL; +import static org.ballerinalang.central.client.CentralClientConstants.BALLERINA_CENTRAL_TELEMETRY_DISABLED; import static org.ballerinalang.central.client.CentralClientConstants.BALLERINA_PLATFORM; import static org.ballerinalang.central.client.CentralClientConstants.CONTENT_DISPOSITION; import static org.ballerinalang.central.client.CentralClientConstants.CONTENT_TYPE; @@ -99,6 +100,7 @@ import static org.ballerinalang.central.client.CentralClientConstants.USER_AGENT; import static org.ballerinalang.central.client.CentralClientConstants.VERSION; import static org.ballerinalang.central.client.Utils.ProgressRequestBody; +import static org.ballerinalang.central.client.Utils.SET_TEST_MODE_ACTIVE; import static org.ballerinalang.central.client.Utils.checkHash; import static org.ballerinalang.central.client.Utils.createBalaInHomeRepo; import static org.ballerinalang.central.client.Utils.getAsList; @@ -1681,16 +1683,14 @@ protected void closeClient(OkHttpClient client) throws IOException { * @return Http request builder */ protected Request.Builder getNewRequest(String supportedPlatform, String ballerinaVersion) { - if (this.accessToken.isEmpty()) { - return new Request.Builder() - .addHeader(BALLERINA_PLATFORM, supportedPlatform) - .addHeader(USER_AGENT, ballerinaVersion); - } else { - return new Request.Builder() - .addHeader(BALLERINA_PLATFORM, supportedPlatform) - .addHeader(USER_AGENT, ballerinaVersion) - .addHeader(AUTHORIZATION, getBearerToken(this.accessToken)); + Request.Builder requestBuilder = new Request.Builder() + .addHeader(BALLERINA_PLATFORM, supportedPlatform) + .addHeader(USER_AGENT, ballerinaVersion) + .addHeader(BALLERINA_CENTRAL_TELEMETRY_DISABLED, String.valueOf(SET_TEST_MODE_ACTIVE)); + if (!this.accessToken.isEmpty()) { + requestBuilder.addHeader(AUTHORIZATION, getBearerToken(this.accessToken)); } + return requestBuilder; } public String accessToken() { diff --git a/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralClientConstants.java b/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralClientConstants.java index 933ebaa160f2..5328e70727c2 100644 --- a/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralClientConstants.java +++ b/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralClientConstants.java @@ -60,5 +60,6 @@ private CentralClientConstants() { public static final int UPDATE_INTERVAL_MILLIS = 1000; public static final String SHA256 = "sha-256="; public static final String SHA256_ALGORITHM = "SHA-256"; - + public static final String TEST_MODE_ACTIVE = "TEST_MODE_ACTIVE"; + public static final String BALLERINA_CENTRAL_TELEMETRY_DISABLED = "Ballerina-Central-Telemetry-Disabled"; } diff --git a/cli/central-client/src/main/java/org/ballerinalang/central/client/Utils.java b/cli/central-client/src/main/java/org/ballerinalang/central/client/Utils.java index 0c9371d5af8f..1f968a104cc6 100644 --- a/cli/central-client/src/main/java/org/ballerinalang/central/client/Utils.java +++ b/cli/central-client/src/main/java/org/ballerinalang/central/client/Utils.java @@ -72,6 +72,7 @@ import static org.ballerinalang.central.client.CentralClientConstants.SHA256; import static org.ballerinalang.central.client.CentralClientConstants.SHA256_ALGORITHM; import static org.ballerinalang.central.client.CentralClientConstants.STAGING_REPO; +import static org.ballerinalang.central.client.CentralClientConstants.TEST_MODE_ACTIVE; import static org.ballerinalang.central.client.CentralClientConstants.UPDATE_INTERVAL_MILLIS; /** @@ -85,6 +86,7 @@ public class Utils { System.getenv(BALLERINA_STAGE_CENTRAL)); public static final boolean SET_BALLERINA_DEV_CENTRAL = Boolean.parseBoolean( System.getenv(BALLERINA_DEV_CENTRAL)); + public static final boolean SET_TEST_MODE_ACTIVE = Boolean.parseBoolean(System.getenv(TEST_MODE_ACTIVE)); private Utils() { } @@ -161,12 +163,13 @@ public static void createBalaInHomeRepo(Response balaDownloadResponse, Path pkgP downloadBody.ifPresent(ResponseBody::close); throw new PackageAlreadyExistsException( logFormatter.formatLog("package already exists in the home repository: " + - balaCacheWithPkgPath.toString())); + balaCacheWithPkgPath.toString()), validPkgVersion); } } catch (IOException e) { downloadBody.ifPresent(ResponseBody::close); throw new PackageAlreadyExistsException( - logFormatter.formatLog("error accessing bala : " + balaCacheWithPkgPath.toString())); + logFormatter.formatLog("error accessing bala : " + balaCacheWithPkgPath.toString()), + validPkgVersion); } // Create the following temp path diff --git a/cli/central-client/src/main/java/org/ballerinalang/central/client/exceptions/PackageAlreadyExistsException.java b/cli/central-client/src/main/java/org/ballerinalang/central/client/exceptions/PackageAlreadyExistsException.java index 07103eb3f773..bbe467e77b47 100644 --- a/cli/central-client/src/main/java/org/ballerinalang/central/client/exceptions/PackageAlreadyExistsException.java +++ b/cli/central-client/src/main/java/org/ballerinalang/central/client/exceptions/PackageAlreadyExistsException.java @@ -25,7 +25,14 @@ */ public class PackageAlreadyExistsException extends CentralClientException { - public PackageAlreadyExistsException(String message) { + private final String version; + + public PackageAlreadyExistsException(String message, String version) { super(message); + this.version = version; + } + + public String version() { + return version; } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/ReferenceFinder.java b/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/ReferenceFinder.java index 8b20558e6350..760d26ce9e14 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/ReferenceFinder.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/ReferenceFinder.java @@ -898,7 +898,7 @@ public void visit(BLangInvocation invocationExpr) { @Override public void visit(BLangTypeInit typeInit) { find(typeInit.userDefinedType); - find(typeInit.argsExpr); + find(typeInit.initInvocation); } @Override diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BalaWriter.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BalaWriter.java index 971046ec3558..960615af9fec 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BalaWriter.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BalaWriter.java @@ -53,9 +53,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipOutputStream; @@ -65,6 +68,7 @@ import static io.ballerina.projects.util.ProjectConstants.DEPENDENCY_GRAPH_JSON; import static io.ballerina.projects.util.ProjectConstants.PACKAGE_JSON; import static io.ballerina.projects.util.ProjectUtils.getBalaName; +import static io.ballerina.projects.util.ProjectUtils.getConflictingResourcesMsg; /** * {@code BalaWriter} writes a package to bala format. @@ -99,9 +103,9 @@ protected BalaWriter() { */ public Path write(Path balaPath) { String balaName = getBalaName(this.packageContext.packageOrg().value(), - this.packageContext.packageName().value(), - this.packageContext.packageVersion().value().toString(), - this.target); + this.packageContext.packageName().value(), + this.packageContext.packageVersion().value().toString(), + this.target); // Create the archive over write if exists try (ZipOutputStream balaOutputStream = new ZipOutputStream( new FileOutputStream(String.valueOf(balaPath.resolve(balaName))))) { @@ -126,9 +130,10 @@ private void populateBalaArchive(ZipOutputStream balaOutputStream) addBalaJson(balaOutputStream); addPackageDoc(balaOutputStream, - this.packageContext.project().sourceRoot(), - this.packageContext.packageName().toString()); + this.packageContext.project().sourceRoot(), + this.packageContext.packageName().toString()); addPackageSource(balaOutputStream); + addResources(balaOutputStream); addIncludes(balaOutputStream); Optional platformLibs = addPlatformLibs(balaOutputStream); addPackageJson(balaOutputStream, platformLibs); @@ -151,8 +156,8 @@ private void addBalaJson(ZipOutputStream balaOutputStream) { private void addPackageJson(ZipOutputStream balaOutputStream, Optional platformLibs) { PackageJson packageJson = new PackageJson(this.packageContext.packageOrg().toString(), - this.packageContext.packageName().toString(), - this.packageContext.packageVersion().toString()); + this.packageContext.packageName().toString(), + this.packageContext.packageVersion().toString()); PackageManifest packageManifest = this.packageContext.packageManifest(); packageJson.setLicenses(packageManifest.license()); @@ -238,7 +243,7 @@ private static Boolean isAllPlatformDepsGraalvmCompatible(Map platforms) { + Map platforms) { for (Map.Entry platform : platforms.entrySet()) { if (!platform.getKey().equals(target) && platform.getValue().graalvmCompatible() != null) { return platform.getKey(); @@ -253,66 +258,51 @@ private void addPackageDoc(ZipOutputStream balaOutputStream, Path packageSourceD throws IOException { final String packageMdFileName = "Package.md"; final String moduleMdFileName = "Module.md"; - final String readmeMdFileName = "README.md"; - Path docsDirInBala = Paths.get(BALA_DOCS_DIR); Path packageMd = packageSourceDir.resolve(packageMdFileName); + Path docsDirInBala = Paths.get(BALA_DOCS_DIR); + + // If `Package.md` exists, create the docs directory & add `Package.md` + if (packageMd.toFile().exists()) { + Path packageMdInBala = docsDirInBala.resolve(packageMdFileName); + putZipEntry(balaOutputStream, packageMdInBala, + new FileInputStream(String.valueOf(packageMd))); + } // If `icon` mentioned in the Ballerina.toml, add it to docs directory String icon = this.packageContext.packageManifest().icon(); if (icon != null && !icon.isEmpty()) { Path iconPath = getIconPath(icon); Path iconInBala = docsDirInBala.resolve(iconPath.getFileName()); - try (FileInputStream inputStream = new FileInputStream(String.valueOf(iconPath))) { - putZipEntry(balaOutputStream, iconInBala, inputStream); - } + putZipEntry(balaOutputStream, iconInBala, new FileInputStream(String.valueOf(iconPath))); } - // If Package.md and Module.md does not exist, pack README.md - if (!packageMd.toFile().exists()) { - packModulesToBala(pkgName, readmeMdFileName, balaOutputStream, packageSourceDir); - } else { - // Creates the docs directory & add `Package.md` - Path packageMdInBala = docsDirInBala.resolve(packageMdFileName); - try (FileInputStream inputStream = new FileInputStream(String.valueOf(packageMd))) { - putZipEntry(balaOutputStream, packageMdInBala, inputStream); - } + // If `Module.md` of default module exists, create `docs/modules` directory & add `Module.md` + Path defaultModuleMd = packageSourceDir.resolve(moduleMdFileName); + Path modulesDirInBalaDocs = docsDirInBala.resolve(MODULES_ROOT); - // Packs the module.md of default and non-default modules - packModulesToBala(pkgName, moduleMdFileName, balaOutputStream, packageSourceDir); + if (defaultModuleMd.toFile().exists()) { + Path defaultModuleMdInBalaDocs = modulesDirInBalaDocs.resolve(pkgName).resolve(moduleMdFileName); + putZipEntry(balaOutputStream, defaultModuleMdInBalaDocs, + new FileInputStream(String.valueOf(defaultModuleMd))); } - } - private void packModulesToBala(String pkgName, String fileName, ZipOutputStream balaOutputStream, - Path packageSourceDir) - throws IOException { - - Path defaultMd = packageSourceDir.resolve(fileName); + // Add other module docs File modulesSourceDir = new File(String.valueOf(packageSourceDir.resolve(MODULES_ROOT))); - Path modulesDirInBalaDocs = Paths.get(BALA_DOCS_DIR).resolve(MODULES_ROOT); - - // Packs default Module - if (defaultMd.toFile().exists()) { - Path defaultMdInBala = modulesDirInBalaDocs.resolve(pkgName).resolve(fileName); - try (FileInputStream inputStream = new FileInputStream(String.valueOf(defaultMd))) { - putZipEntry(balaOutputStream, defaultMdInBala, inputStream); - } - } - // Packs non-default modules File[] directoryListing = modulesSourceDir.listFiles(); + if (directoryListing != null) { for (File moduleDir : directoryListing) { if (moduleDir.isDirectory()) { - // Gets filename path - Path nonDefaultModuleMd = packageSourceDir.resolve(MODULES_ROOT).resolve(moduleDir.getName()) - .resolve(fileName); - // Creates `package.module` folder, if filename path exists - if (nonDefaultModuleMd.toFile().exists()) { - Path nonDefaultModuleMdInBalaDocs = modulesDirInBalaDocs - .resolve(pkgName + "." + moduleDir.getName()).resolve(fileName); - try (FileInputStream inputStream = new FileInputStream(String.valueOf(nonDefaultModuleMd))) { - putZipEntry(balaOutputStream, nonDefaultModuleMdInBalaDocs, inputStream); - } + // Get `Module.md` path + Path otherModuleMd = packageSourceDir.resolve(MODULES_ROOT).resolve(moduleDir.getName()) + .resolve(moduleMdFileName); + // Create `package.module` folder, if `Module.md` path exists + if (otherModuleMd.toFile().exists()) { + Path otherModuleMdInBalaDocs = modulesDirInBalaDocs.resolve(pkgName + "." + moduleDir.getName()) + .resolve(moduleMdFileName); + putZipEntry(balaOutputStream, otherModuleMdInBalaDocs, + new FileInputStream(String.valueOf(otherModuleMd))); } } } @@ -324,14 +314,6 @@ private void addPackageSource(ZipOutputStream balaOutputStream) throws IOExcepti for (ModuleId moduleId : this.packageContext.moduleIds()) { Module module = this.packageContext.project().currentPackage().module(moduleId); - // copy resources - for (DocumentId documentId : module.resourceIds()) { - Resource resource = module.resource(documentId); - Path resourcePath = Paths.get(ProjectConstants.MODULES_ROOT).resolve(module.moduleName().toString()) - .resolve(RESOURCE_DIR_NAME).resolve(resource.name()); - putZipEntry(balaOutputStream, resourcePath, new ByteArrayInputStream(resource.content())); - } - // Generate empty bal file for default module in tools if (module.isDefaultModule() && packageContext.balToolTomlContext().isPresent() && module.documentIds().isEmpty()) { @@ -356,12 +338,44 @@ private void addPackageSource(ZipOutputStream balaOutputStream) throws IOExcepti char[] documentContent = document.textDocument().toCharArray(); putZipEntry(balaOutputStream, documentPath, - new ByteArrayInputStream(new String(documentContent).getBytes(StandardCharsets.UTF_8))); + new ByteArrayInputStream(new String(documentContent).getBytes(StandardCharsets.UTF_8))); } } } } + private void addResources(ZipOutputStream balaOutputStream) throws IOException { + Set resourceFiles = new HashSet<>(); + + // copy resources + for (DocumentId documentId : packageContext.resourceIds()) { + String resourceFile = packageContext.resourceContext(documentId).name(); + Path resourcePath = Paths.get(RESOURCE_DIR_NAME).resolve(resourceFile); + if (resourceFiles.add(resourcePath.toString())) { + putZipEntry(balaOutputStream, resourcePath, new ByteArrayInputStream( + packageContext.resourceContext(documentId).content())); + } + } + + // copy resources from `target/resources` + if (packageContext.project().kind().equals(ProjectKind.BUILD_PROJECT)) { + Map cachedResources = ProjectUtils.getAllGeneratedResources( + packageContext.project().generatedResourcesDir()); + List conflictingResourceFiles = cachedResources.keySet().stream() + .filter(path -> !resourceFiles.add(path)) + .collect(Collectors.toList()); + + if (!conflictingResourceFiles.isEmpty()) { + throw new ProjectException(getConflictingResourcesMsg( + packageContext.descriptor().toString(), conflictingResourceFiles)); + } + + for (Map.Entry entry : cachedResources.entrySet()) { + putZipEntry(balaOutputStream, Paths.get(entry.getKey()), new ByteArrayInputStream(entry.getValue())); + } + } + } + private void addIncludes(ZipOutputStream balaOutputStream) throws IOException { List includePatterns = this.packageContext.packageManifest().includes(); List includePaths = ProjectUtils.getPathsMatchingIncludePatterns( @@ -399,7 +413,7 @@ private void addDependenciesJson(ZipOutputStream balaOutputStream) { try { putZipEntry(balaOutputStream, Paths.get(DEPENDENCY_GRAPH_JSON), - new ByteArrayInputStream(gson.toJson(depGraphJson).getBytes(Charset.defaultCharset()))); + new ByteArrayInputStream(gson.toJson(depGraphJson).getBytes(Charset.defaultCharset()))); } catch (IOException e) { throw new ProjectException("Failed to write '" + DEPENDENCY_GRAPH_JSON + "' file: " + e.getMessage(), e); } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildOptions.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildOptions.java index 71c2d928b7b9..709140908558 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildOptions.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildOptions.java @@ -25,16 +25,16 @@ public class BuildOptions { private final Boolean showDependencyDiagnostics; - private Boolean testReport; - private Boolean codeCoverage; - private Boolean dumpBuildTime; - private Boolean skipTests; - private CompilationOptions compilationOptions; - private String targetDir; - private Boolean enableCache; - private Boolean nativeImage; - private Boolean exportComponentModel; - private String graalVMBuildOptions; + private final Boolean testReport; + private final Boolean codeCoverage; + private final Boolean dumpBuildTime; + private final Boolean skipTests; + private final CompilationOptions compilationOptions; + private final String targetDir; + private final Boolean enableCache; + private final Boolean nativeImage; + private final Boolean exportComponentModel; + private final String graalVMBuildOptions; BuildOptions(Boolean testReport, Boolean codeCoverage, Boolean dumpBuildTime, Boolean skipTests, CompilationOptions compilationOptions, String targetPath, Boolean enableCache, @@ -212,6 +212,7 @@ public BuildOptions acceptTheirs(BuildOptions theirOptions) { buildOptionsBuilder.setExportComponentModel(compilationOptions.exportComponentModel); buildOptionsBuilder.setEnableCache(compilationOptions.enableCache); buildOptionsBuilder.setRemoteManagement(compilationOptions.remoteManagement); + buildOptionsBuilder.setOptimizeDependencyCompilation(compilationOptions.optimizeDependencyCompilation); return buildOptionsBuilder.build(); } @@ -250,7 +251,8 @@ public enum OptionName { NATIVE_IMAGE("graalvm"), EXPORT_COMPONENT_MODEL("exportComponentModel"), GRAAL_VM_BUILD_OPTIONS("graalvmBuildOptions"), - SHOW_DEPENDENCY_DIAGNOSTICS("showDependencyDiagnostics"); + SHOW_DEPENDENCY_DIAGNOSTICS("showDependencyDiagnostics"), + OPTIMIZE_DEPENDENCY_COMPILATION("optimizeDependencyCompilation"); private final String name; @@ -283,7 +285,6 @@ public static class BuildOptionsBuilder { private String graalVMBuildOptions; private Boolean showDependencyDiagnostics; - private BuildOptionsBuilder() { compilationOptionsBuilder = CompilationOptions.builder(); } @@ -415,6 +416,17 @@ public BuildOptionsBuilder setShowDependencyDiagnostics(Boolean value) { showDependencyDiagnostics = value; return this; } + + /** + * (Experimental) option to specify that the memory usage must be optimized. + * + * @param value true or false (default) + * @return BuildOptionsBuilder instance + */ + public BuildOptionsBuilder setOptimizeDependencyCompilation(Boolean value) { + compilationOptionsBuilder.setOptimizeDependencyCompilation(value); + return this; + } public BuildOptions build() { CompilationOptions compilationOptions = compilationOptionsBuilder.build(); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CodeGeneratorManager.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CodeGeneratorManager.java index 65267bcadd4c..1e08e766c59e 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CodeGeneratorManager.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CodeGeneratorManager.java @@ -333,6 +333,7 @@ public void addTestSourceFile(TextDocument textDocument, String filenamePrefix) } @Override + @Deprecated(since = "2201.10.0", forRemoval = true) public void addResourceFile(byte[] content, String fileName, ModuleId moduleId) { if (currentPackage.moduleIds().contains(moduleId)) { resourceFiles.add(new GeneratedResourceFile(content, fileName, moduleId)); @@ -343,11 +344,13 @@ public void addResourceFile(byte[] content, String fileName, ModuleId moduleId) } @Override + @Deprecated(since = "2201.10.0", forRemoval = true) public void addResourceFile(byte[] content, String fileName) { addResourceFile(content, fileName, defaultModuleId); } @Override + @Deprecated(since = "2201.10.0", forRemoval = true) public void addTestResourceFile(byte[] content, String fileName, ModuleId moduleId) { if (currentPackage.moduleIds().contains(moduleId)) { testResourceFiles.add(new GeneratedTestResourceFile(content, fileName, moduleId)); @@ -358,6 +361,7 @@ public void addTestResourceFile(byte[] content, String fileName, ModuleId module } @Override + @Deprecated(since = "2201.10.0", forRemoval = true) public void addTestResourceFile(byte[] content, String fileName) { addTestResourceFile(content, fileName, defaultModuleId); } @@ -646,11 +650,13 @@ CodeGeneratorTaskResultBuilder addTestSourceFiles(Collection return this; } + @Deprecated(since = "2201.10.0", forRemoval = true) CodeGeneratorTaskResultBuilder addResourceFiles(Collection resourceFiles) { generatedResourceFiles.addAll(resourceFiles); return this; } + @Deprecated(since = "2201.10.0", forRemoval = true) CodeGeneratorTaskResultBuilder addTestResourceFiles(Collection testResourceFiles) { generatedTestResourceFiles.addAll(testResourceFiles); return this; @@ -749,6 +755,7 @@ private Package modifyModule(ModuleId moduleId, Package pkg, CodeGenTaskResult c return modifier.apply().packageInstance(); } + @Deprecated(since = "2201.10.0", forRemoval = true) private void addGeneratedResource(String newResourceFilename, byte[] content, Module.Modifier modifier, @@ -758,6 +765,7 @@ private void addGeneratedResource(String newResourceFilename, modifier.addResource(resourceConfig); } + @Deprecated(since = "2201.10.0", forRemoval = true) private void addGeneratedTestResource(String newTestResourceFilename, byte[] content, Module.Modifier modifier, diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilationOptions.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilationOptions.java index a7214a614199..31b5a927fa2e 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilationOptions.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilationOptions.java @@ -40,13 +40,14 @@ public class CompilationOptions { Boolean enableCache; Boolean disableSyntaxTree; Boolean remoteManagement; + Boolean optimizeDependencyCompilation; CompilationOptions(Boolean offlineBuild, Boolean observabilityIncluded, Boolean dumpBir, Boolean dumpBirFile, String cloud, Boolean listConflictedClasses, Boolean sticky, Boolean dumpGraph, Boolean dumpRawGraphs, Boolean withCodeGenerators, Boolean withCodeModifiers, Boolean configSchemaGen, Boolean exportOpenAPI, Boolean exportComponentModel, Boolean enableCache, Boolean disableSyntaxTree, - Boolean remoteManagement) { + Boolean remoteManagement, Boolean optimizeDependencyCompilation) { this.offlineBuild = offlineBuild; this.observabilityIncluded = observabilityIncluded; this.dumpBir = dumpBir; @@ -64,6 +65,7 @@ public class CompilationOptions { this.enableCache = enableCache; this.disableSyntaxTree = disableSyntaxTree; this.remoteManagement = remoteManagement; + this.optimizeDependencyCompilation = optimizeDependencyCompilation; } public boolean offlineBuild() { @@ -130,6 +132,10 @@ boolean remoteManagement() { return toBooleanDefaultIfNull(this.remoteManagement); } + boolean optimizeDependencyCompilation() { + return toBooleanDefaultIfNull(this.optimizeDependencyCompilation); + } + /** * Merge the given compilation options by favoring theirs if there are conflicts. * @@ -218,6 +224,11 @@ CompilationOptions acceptTheirs(CompilationOptions theirOptions) { } else { compilationOptionsBuilder.setRemoteManagement(this.remoteManagement); } + if (theirOptions.optimizeDependencyCompilation != null) { + compilationOptionsBuilder.setOptimizeDependencyCompilation(theirOptions.optimizeDependencyCompilation); + } else { + compilationOptionsBuilder.setOptimizeDependencyCompilation(this.optimizeDependencyCompilation); + } return compilationOptionsBuilder.build(); } @@ -273,6 +284,7 @@ public static class CompilationOptionsBuilder { private Boolean enableCache; private Boolean disableSyntaxTree; private Boolean remoteManagement; + private Boolean optimizeDependencyCompilation; public CompilationOptionsBuilder setOffline(Boolean value) { offline = value; @@ -359,11 +371,17 @@ public CompilationOptionsBuilder setRemoteManagement(Boolean value) { return this; } + public CompilationOptionsBuilder setOptimizeDependencyCompilation(Boolean value) { + optimizeDependencyCompilation = value; + return this; + } + public CompilationOptions build() { return new CompilationOptions(offline, observabilityIncluded, dumpBir, dumpBirFile, cloud, listConflictedClasses, sticky, dumpGraph, dumpRawGraph, withCodeGenerators, withCodeModifiers, configSchemaGen, exportOpenAPI, - exportComponentModel, enableCache, disableSyntaxTree, remoteManagement); + exportComponentModel, enableCache, disableSyntaxTree, remoteManagement, + optimizeDependencyCompilation); } } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilerBackend.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilerBackend.java index 794134edfba1..548e78c8e957 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilerBackend.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilerBackend.java @@ -68,6 +68,8 @@ public abstract Collection platformLibraryDependencies(PackageI */ public abstract PlatformLibrary codeGeneratedTestLibrary(PackageId packageId, ModuleName moduleName); + public abstract PlatformLibrary codeGeneratedResourcesLibrary(PackageId packageId); + /** * Returns the platform-specific runtime library. * diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ConfigReader.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ConfigReader.java index 79d94c92c050..3a688369e253 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ConfigReader.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ConfigReader.java @@ -137,7 +137,7 @@ private static boolean isDirectDependency(Collection moduleDep dependency.descriptor().packageName().value().equals(packageName) && (moduleName == null ? dependency.descriptor().name().moduleNamePart() == null - : dependency.descriptor().name().moduleNamePart().equals(moduleName)) + : moduleName.equals(dependency.descriptor().name().moduleNamePart())) ); } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/DocumentContext.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/DocumentContext.java index b91b222fe5a5..c219679275d7 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/DocumentContext.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/DocumentContext.java @@ -196,6 +196,8 @@ void shrink() { if (this.compilationUnit != null) { this.compilationUnit.topLevelNodes.clear(); } + this.syntaxTree = null; + this.moduleLoadRequests = null; this.content = null; } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/JBallerinaBackend.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/JBallerinaBackend.java index 995dbbdc9cb4..4facf9078f8c 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/JBallerinaBackend.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/JBallerinaBackend.java @@ -22,7 +22,6 @@ import io.ballerina.projects.internal.DefaultDiagnosticResult; import io.ballerina.projects.internal.PackageDiagnostic; import io.ballerina.projects.internal.ProjectDiagnosticErrorCode; -import io.ballerina.projects.internal.jballerina.JarWriter; import io.ballerina.projects.internal.model.Target; import io.ballerina.projects.util.ProjectConstants; import io.ballerina.projects.util.ProjectUtils; @@ -38,8 +37,8 @@ import org.ballerinalang.maven.MavenResolver; import org.ballerinalang.maven.Utils; import org.ballerinalang.maven.exceptions.MavenResolverException; -import org.wso2.ballerinalang.compiler.CompiledJarFile; import org.wso2.ballerinalang.compiler.bir.codegen.CodeGenerator; +import org.wso2.ballerinalang.compiler.bir.codegen.CompiledJarFile; import org.wso2.ballerinalang.compiler.bir.codegen.interop.InteropValidator; import org.wso2.ballerinalang.compiler.tree.BLangPackage; import org.wso2.ballerinalang.compiler.util.CompilerContext; @@ -78,6 +77,8 @@ import static io.ballerina.projects.util.FileUtils.getFileNameWithoutExtension; import static io.ballerina.projects.util.ProjectConstants.BIN_DIR_NAME; import static io.ballerina.projects.util.ProjectConstants.DOT; +import static io.ballerina.projects.util.ProjectConstants.RESOURCE_DIR_NAME; +import static io.ballerina.projects.util.ProjectUtils.getConflictingResourcesMsg; import static io.ballerina.projects.util.ProjectUtils.getThinJarFileName; import static org.wso2.ballerinalang.compiler.bir.codegen.JvmConstants.CLASS_FILE_SUFFIX; @@ -95,6 +96,7 @@ public class JBallerinaBackend extends CompilerBackend { private static final String JAR_FILE_NAME_SUFFIX = ""; private static final HashSet excludeExtensions = new HashSet<>(Lists.of("DSA", "SF")); private static final String OS = System.getProperty("os.name").toLowerCase(Locale.getDefault()); + public static final String JAR_NAME_SEPARATOR = "-"; private final PackageResolution pkgResolution; private final JvmTarget jdkVersion; @@ -108,6 +110,7 @@ public class JBallerinaBackend extends CompilerBackend { private DiagnosticResult diagnosticResult; private boolean codeGenCompleted; private final List conflictedJars; + List conflictedResourcesDiagnostics = new ArrayList<>(); public static JBallerinaBackend from(PackageCompilation packageCompilation, JvmTarget jdkVersion) { return from(packageCompilation, jdkVersion, true); @@ -159,6 +162,9 @@ private void performCodeGen(boolean shrink) { // collect compilation diagnostics List moduleDiagnostics = new ArrayList<>(); for (ModuleContext moduleContext : pkgResolution.topologicallySortedModuleList()) { + if (shrink) { + ModuleContext.shrinkDocuments(moduleContext); + } if (moduleContext.moduleId().packageId().equals(packageContext.packageId())) { if (packageCompilation.diagnosticResult().hasErrors()) { for (Diagnostic diagnostic : moduleContext.diagnostics()) { @@ -181,17 +187,17 @@ private void performCodeGen(boolean shrink) { } } - if (shrink) { - ModuleContext.shrinkDocuments(moduleContext); - } if (moduleContext.project().kind() == ProjectKind.BALA_PROJECT) { moduleContext.cleanBLangPackage(); } } + // add compilation diagnostics diagnostics.addAll(moduleDiagnostics); // add plugin diagnostics diagnostics.addAll(this.packageContext.getPackageCompilation().pluginDiagnostics()); + // add conflicting resources diagnostics + diagnostics.addAll(conflictedResourcesDiagnostics); this.diagnosticResult = new DefaultDiagnosticResult(diagnostics); codeGenCompleted = true; @@ -345,6 +351,11 @@ public PlatformLibrary codeGeneratedTestLibrary(PackageId packageId, ModuleName TEST_JAR_FILE_NAME_SUFFIX + JAR_FILE_NAME_SUFFIX); } + @Override + public PlatformLibrary codeGeneratedResourcesLibrary(PackageId packageId) { + return codeGeneratedResourcesLibrary(packageId, PlatformLibraryScope.DEFAULT); + } + @Override public PlatformLibrary runtimeLibrary() { return new JarLibrary(ProjectUtils.getBallerinaRTJarPath(), PlatformLibraryScope.DEFAULT); @@ -370,11 +381,15 @@ public void performCodeGen(ModuleContext moduleContext, CompilationCache compila } String jarFileName = getJarFileName(moduleContext) + JAR_FILE_NAME_SUFFIX; try { - ByteArrayOutputStream byteStream = JarWriter.write(compiledJarFile, getResources(moduleContext)); + ByteArrayOutputStream byteStream = compiledJarFile.toByteArrayStream(); compilationCache.cachePlatformSpecificLibrary(this, jarFileName, byteStream); } catch (IOException e) { throw new ProjectException("Failed to cache generated jar, module: " + moduleContext.moduleName()); } + if (moduleContext.project().currentPackage().packageContext() == packageContext && + moduleContext.isDefaultModule()) { + cacheResources(compilationCache, moduleContext.project().buildOptions().skipTests()); + } // skip generation of the test jar if --with-tests option is not provided if (moduleContext.project().buildOptions().skipTests()) { return; @@ -388,7 +403,7 @@ public void performCodeGen(ModuleContext moduleContext, CompilationCache compila CompiledJarFile compiledTestJarFile = jvmCodeGenerator.generateTestModule(bLangPackage.testablePkgs.get(0), isRemoteMgtEnabled); try { - ByteArrayOutputStream byteStream = JarWriter.write(compiledTestJarFile, getAllResources(moduleContext)); + ByteArrayOutputStream byteStream = compiledTestJarFile.toByteArrayStream(); compilationCache.cachePlatformSpecificLibrary(this, testJarFileName, byteStream); } catch (IOException e) { throw new ProjectException("Failed to cache generated test jar, module: " + moduleContext.moduleName()); @@ -560,59 +575,61 @@ private Manifest createTestManifest() { private void copyJar(ZipArchiveOutputStream outStream, JarLibrary jarLibrary, HashMap copiedEntries, HashMap services) throws IOException { - - ZipFile zipFile = new ZipFile(jarLibrary.path().toFile()); - ZipArchiveEntryPredicate predicate = entry -> { - String entryName = entry.getName(); - if (entryName.equals("META-INF/MANIFEST.MF")) { - return false; - } - if (entryName.equals("module-info.class")) { - return false; - } - if (entryName.startsWith("META-INF/services")) { - StringBuilder s = services.get(entryName); - if (s == null) { - s = new StringBuilder(); - services.put(entryName, s); + if (Thread.currentThread().isInterrupted()) { + return; + } + try (ZipFile zipFile = new ZipFile(jarLibrary.path().toFile())) { + ZipArchiveEntryPredicate predicate = entry -> { + String entryName = entry.getName(); + if (entryName.equals("META-INF/MANIFEST.MF")) { + return false; + } + if (entryName.equals("module-info.class")) { + return false; } - char c = '\n'; + if (entryName.startsWith("META-INF/services")) { + StringBuilder s = services.get(entryName); + if (s == null) { + s = new StringBuilder(); + services.put(entryName, s); + } + char c = '\n'; - int len; - try (BufferedInputStream inStream = new BufferedInputStream(zipFile.getInputStream(entry))) { - while ((len = inStream.read()) != -1) { - c = (char) len; - s.append(c); + int len; + try (BufferedInputStream inStream = new BufferedInputStream(zipFile.getInputStream(entry))) { + while ((len = inStream.read()) != -1) { + c = (char) len; + s.append(c); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + if (c != '\n') { + s.append('\n'); } - } catch (IOException e) { - throw new RuntimeException(e); - } - if (c != '\n') { - s.append('\n'); - } - // Its not required to copy SPI entries in here as we'll be adding merged SPI related entries - // separately. Therefore the predicate should be set as false. - return false; - } + // Its not required to copy SPI entries in here as we'll be adding merged SPI related entries + // separately. Therefore the predicate should be set as false. + return false; + } - // Skip already copied files or excluded extensions. - if (isCopiedEntry(entryName, copiedEntries)) { - addConflictedJars(jarLibrary, copiedEntries, entryName); - return false; - } - if (isExcludedEntry(entryName)) { - return false; - } - // SPIs will be merged first and then put into jar separately. - copiedEntries.put(entryName, jarLibrary); - return true; - }; + // Skip already copied files or excluded extensions. + if (isCopiedEntry(entryName, copiedEntries)) { + addConflictedJars(jarLibrary, copiedEntries, entryName); + return false; + } + if (isExcludedEntry(entryName)) { + return false; + } + // SPIs will be merged first and then put into jar separately. + copiedEntries.put(entryName, jarLibrary); + return true; + }; - // Transfers selected entries from this zip file to the output stream, while preserving its compression and - // all the other original attributes. - zipFile.copyRawEntries(outStream, predicate); - zipFile.close(); + // Transfers selected entries from this zip file to the output stream, while preserving its compression and + // all the other original attributes. + zipFile.copyRawEntries(outStream, predicate); + } } private static boolean isCopiedEntry(String entryName, HashMap copiedEntries) { @@ -750,32 +767,6 @@ private Path emitGraalExecutable(Path executableFilePath, List emitR return Path.of(FilenameUtils.removeExtension(executableFilePath.toString())); } - private Map getResources(ModuleContext moduleContext) { - Map resourceMap = new HashMap<>(); - for (DocumentId documentId : moduleContext.resourceIds()) { - String resourceName = ProjectConstants.RESOURCE_DIR_NAME + "/" - + moduleContext.descriptor().org().toString() + "/" - + moduleContext.moduleName().toString() + "/" - + moduleContext.descriptor().version().value().major() + "/" - + moduleContext.resourceContext(documentId).name(); - resourceMap.put(resourceName, moduleContext.resourceContext(documentId).content()); - } - return resourceMap; - } - - private Map getAllResources(ModuleContext moduleContext) { - Map resourceMap = getResources(moduleContext); - for (DocumentId documentId : moduleContext.testResourceIds()) { - String resourceName = ProjectConstants.RESOURCE_DIR_NAME + "/" - + moduleContext.descriptor().org() + "/" - + moduleContext.moduleName().toString() + "/" - + moduleContext.descriptor().version().value().major() + "/" - + moduleContext.resourceContext(documentId).name(); - resourceMap.put(resourceName, moduleContext.resourceContext(documentId).content()); - } - return resourceMap; - } - private PlatformLibraryScope getPlatformLibraryScope(Map dependency) { PlatformLibraryScope scope; String scopeValue = (String) dependency.get(JarLibrary.KEY_SCOPE); @@ -950,4 +941,145 @@ private void addProvidedDependencyWarning(List emitResultDiagnostics this.packageContext().descriptor().name().toString())); } } + + private PlatformLibrary codeGeneratedResourcesLibrary(PackageId packageId, PlatformLibraryScope scope) { + Package pkg = packageCache.getPackageOrThrow(packageId); + CompilationCache compilationCache = pkg.project().projectEnvironmentContext().getService( + CompilationCache.class); + return compilationCache.getPlatformSpecificLibrary(this, RESOURCE_DIR_NAME) + .map(path -> new JarLibrary(path, scope)) + .orElse(null); + } + + private Map getPackageResources(PackageContext packageContext) { + Map resourceMap = new HashMap<>(); + for (DocumentId documentId : packageContext.resourceIds()) { + String resourceName = RESOURCE_DIR_NAME + "/" + + packageContext.resourceContext(documentId).name(); + resourceMap.put(resourceName, packageContext.resourceContext(documentId).content()); + } + return resourceMap; + } + + private Map getPackageAndTestResources(PackageContext packageContext) { + Map resourceMap = getPackageResources(packageContext); + for (DocumentId documentId : packageContext.testResourceIds()) { + String resourceName = RESOURCE_DIR_NAME + "/" + + packageContext.resourceContext(documentId).name(); + if (resourceMap.containsKey(resourceName)) { + addConflictingTestResourceDiag(packageContext.descriptor().toString(), resourceName); + } + resourceMap.put(resourceName, packageContext.resourceContext(documentId).content()); + } + return resourceMap; + } + + private void cacheResources(CompilationCache compilationCache, boolean skipTests) { + Map resources = new HashMap<>(); + Map resourceToPkgMap = new HashMap<>(); + List conflictingResourceFiles = new ArrayList<>(); + + // Add resources from dependencies in order + pkgResolution.allDependencies() + .stream() + .filter(pkgDep -> pkgDep.scope() != PackageDependencyScope.TEST_ONLY) + .filter(pkgDep -> !pkgDep.packageInstance().descriptor().isLangLibPackage()) + .map(pkgDep -> pkgDep.packageInstance().packageContext()) + .forEach(pkgContext -> { + Map depResources = getPackageResources(pkgContext); + for (Map.Entry entry : depResources.entrySet()) { + if (resources.containsKey(entry.getKey())) { + addConflictingDepResourceDiag(pkgContext.descriptor().toString(), + resourceToPkgMap.get(entry.getKey()), entry.getKey()); + } + resources.put(entry.getKey(), entry.getValue()); + resourceToPkgMap.put(entry.getKey(), pkgContext.descriptor().toString()); + } + }); + // Add resources from the package + Map packageResources = skipTests ? getPackageResources(packageContext) : + getPackageAndTestResources(packageContext); + for (Map.Entry entry : packageResources.entrySet()) { + if (resources.containsKey(entry.getKey())) { + addConflictingDepResourceDiag(packageContext.descriptor().toString(), + resourceToPkgMap.get(entry.getKey()), entry.getKey()); + } + resources.put(entry.getKey(), entry.getValue()); + resourceToPkgMap.put(entry.getKey(), packageContext.descriptor().toString()); + } + + // Add generated resources and check for conflicts for build projects + if (!this.packageContext().project().kind().equals(ProjectKind.BALA_PROJECT)) { + Map generatedResources = ProjectUtils.getAllGeneratedResources( + packageContext.project().generatedResourcesDir()); + for (Map.Entry entry : generatedResources.entrySet()) { + if (resources.containsKey(entry.getKey())) { + if (packageResources.containsKey(entry.getKey())) { + conflictingResourceFiles.add(entry.getKey()); + continue; + } + // Issue a warning for conflicts with dependency resources + addConflictingGenResourceDiag(resourceToPkgMap.get(entry.getKey()), + packageContext.descriptor().toString(), entry.getKey()); + } + resources.put(entry.getKey(), entry.getValue()); + } + // Handle conflicting resources + if (!conflictingResourceFiles.isEmpty()) { + throw new ProjectException(getConflictingResourcesMsg(packageContext.descriptor().toString(), + conflictingResourceFiles)); + } + } + + // Cache the resources if there are any + if (!resources.isEmpty()) { + try { + String resourceJarName = RESOURCE_DIR_NAME + JAR_FILE_NAME_SUFFIX; + CompiledJarFile resourceJar = new CompiledJarFile(""); + resourceJar.jarEntries.putResourceEntries(resources); + try (ByteArrayOutputStream byteStream = resourceJar.toByteArrayStream()) { + compilationCache.cachePlatformSpecificLibrary(this, resourceJarName, byteStream); + } + } catch (IOException e) { + throw new ProjectException("Failed to cache resources jar, package: " + + packageContext.packageName(), e); + } + } + } + + private void addConflictingDepResourceDiag(String packageDesc, String existingPackageDesc, String resourceName) { + DiagnosticInfo diagnosticInfo = new DiagnosticInfo( + ProjectDiagnosticErrorCode.CONFLICTING_RESOURCE_FILE.diagnosticId(), + String.format("detected conflicting resource files. The packages '" + + existingPackageDesc + "' and '" + packageDesc + + "' both export a resource with the same name '" + resourceName + + "'. Picking the resource exported by '" + packageDesc + "'."), + DiagnosticSeverity.WARNING); + conflictedResourcesDiagnostics.add(new PackageDiagnostic(diagnosticInfo, + this.packageContext.descriptor().name().toString())); + } + + private void addConflictingGenResourceDiag(String existingPackageDesc, String packageDesc, String resourceName) { + DiagnosticInfo diagnosticInfo = new DiagnosticInfo( + ProjectDiagnosticErrorCode.CONFLICTING_RESOURCE_FILE.diagnosticId(), + String.format("detected conflicting resource files. The package " + existingPackageDesc + + " and the generated resources for the current package '" + packageDesc + + "' both export a resource with the same name '" + resourceName + "'. " + + "Picking the generated resource file."), + DiagnosticSeverity.WARNING); + conflictedResourcesDiagnostics.add(new PackageDiagnostic( + diagnosticInfo, this.packageContext.descriptor().name().toString())); + } + + private void addConflictingTestResourceDiag(String packageDesc, String resourceName) { + DiagnosticInfo diagnosticInfo = new DiagnosticInfo( + ProjectDiagnosticErrorCode.CONFLICTING_RESOURCE_FILE.diagnosticId(), + String.format("detected conflicting resource files. The test specific resources and package " + + "resources for '" + packageDesc + "' both export a resource with the same name '" + + resourceName + "'. Picking the test specific resource."), + DiagnosticSeverity.WARNING); + conflictedResourcesDiagnostics.add(new PackageDiagnostic(diagnosticInfo, + this.packageContext.descriptor().name().toString())); + } + } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/JarResolver.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/JarResolver.java index b037d540fcc3..413f1d5ea7f4 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/JarResolver.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/JarResolver.java @@ -37,6 +37,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import static io.ballerina.identifier.Utils.encodeNonFunctionIdentifier; @@ -110,10 +111,16 @@ public Collection getJarFilePathsRequiredForExecution() { }); // 3) Add the runtime library path + String packageName = getPackageName(rootPackageContext); jarFiles.add(new JarLibrary(jBalBackend.runtimeLibrary().path(), PlatformLibraryScope.DEFAULT, - getPackageName(rootPackageContext))); + packageName)); + // Add resources + Optional.ofNullable(jBalBackend.codeGeneratedResourcesLibrary(rootPackageContext.packageId())) + .ifPresent(library -> jarFiles.add( + new JarLibrary(library.path(), PlatformLibraryScope.DEFAULT, + packageName))); // TODO Filter out duplicate jar entries return jarFiles; } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Module.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Module.java index be720c228e7a..48065a73e1b4 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Module.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Module.java @@ -19,7 +19,6 @@ import java.util.Collection; import java.util.Comparator; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; @@ -42,10 +41,7 @@ public class Module { private final Package packageInstance; private final Map srcDocs; private final Map testSrcDocs; - private final Map resources; - private final Map testResources; private final Function populateDocumentFunc; - private final Function populateResourceFunc; private Optional moduleMd = null; @@ -55,12 +51,8 @@ public class Module { this.srcDocs = new ConcurrentHashMap<>(); this.testSrcDocs = new ConcurrentHashMap<>(); - this.resources = new ConcurrentHashMap<>(); - this.testResources = new ConcurrentHashMap<>(); this.populateDocumentFunc = documentId -> new Document( this.moduleContext.documentContext(documentId), this); - this.populateResourceFunc = documentId -> new Resource( - this.moduleContext.resourceContext(documentId), this); } static Module from(ModuleContext moduleContext, Package packageInstance) { @@ -91,12 +83,14 @@ public Collection testDocumentIds() { return this.moduleContext.testSrcDocumentIds(); } + @Deprecated(since = "2201.10.0", forRemoval = true) public Collection resourceIds() { - return this.moduleContext.resourceIds(); + return this.moduleContext.project().currentPackage().resourceIds(); } + @Deprecated(since = "2201.10.0", forRemoval = true) public Collection testResourceIds() { - return this.moduleContext.testResourceIds(); + return this.moduleContext.project().currentPackage().getDefaultModule().testResourceIds(); } public Document document(DocumentId documentId) { @@ -108,13 +102,10 @@ public Document document(DocumentId documentId) { } } + @Deprecated(since = "2201.10.0", forRemoval = true) public Resource resource(DocumentId documentId) { + return this.packageInstance.resource(documentId); // TODO Should we throw an error if the documentId is not present - if (resourceIds().contains(documentId)) { - return this.resources.computeIfAbsent(documentId, this.populateResourceFunc); - } else { - return this.testResources.computeIfAbsent(documentId, this.populateResourceFunc); - } } @@ -186,8 +177,6 @@ public static class Modifier { private Package packageInstance; private Project project; private MdDocumentContext moduleMdContext; - private final Map resourceContextMap; - private final Map testResourceContextMap; private Modifier(Module oldModule) { moduleId = oldModule.moduleId(); @@ -199,8 +188,6 @@ private Modifier(Module oldModule) { packageInstance = oldModule.packageInstance; project = oldModule.project(); moduleMdContext = oldModule.moduleContext.moduleMdContext().orElse(null); - resourceContextMap = copyResources(oldModule, oldModule.moduleContext.resourceIds()); - testResourceContextMap = copyResources(oldModule, oldModule.moduleContext.testResourceIds()); } Modifier updateDocument(DocumentContext newDocContext) { @@ -218,9 +205,8 @@ Modifier updateDocument(DocumentContext newDocContext) { * @param resourceConfig configurations to create the resource * @return an instance of the Module.Modifier */ + @Deprecated(since = "2201.10.0", forRemoval = true) public Modifier addResource(ResourceConfig resourceConfig) { - ResourceContext newResourceContext = ResourceContext.from(resourceConfig); - this.resourceContextMap.put(newResourceContext.documentId(), newResourceContext); return this; } @@ -230,9 +216,8 @@ public Modifier addResource(ResourceConfig resourceConfig) { * @param resourceConfig configurations to create the test resource * @return an instance of the Module.Modifier */ + @Deprecated(since = "2201.10.0", forRemoval = true) public Modifier addTestResource(ResourceConfig resourceConfig) { - ResourceContext newResourceContext = ResourceContext.from(resourceConfig); - this.testResourceContextMap.put(newResourceContext.documentId(), newResourceContext); return this; } @@ -242,13 +227,8 @@ public Modifier addTestResource(ResourceConfig resourceConfig) { * @param documentId documentId of the resource to remove * @return an instance of the Module.Modifier */ + @Deprecated(since = "2201.10.0", forRemoval = true) public Modifier removeResource(DocumentId documentId) { - - if (this.resourceContextMap.containsKey(documentId)) { - this.resourceContextMap.remove(documentId); - } else { - this.testResourceContextMap.remove(documentId); - } return this; } @@ -313,15 +293,6 @@ public Module apply() { return createNewModule(this.srcDocContextMap, this.testDocContextMap); } - private Map copyResources(Module oldModule, Collection documentIds) { - Map resourceContextMap = new HashMap<>(); - for (DocumentId documentId : documentIds) { - resourceContextMap.put(documentId, oldModule.moduleContext.resourceContext(documentId)); - } - return resourceContextMap; - } - - private Map copySrcDocs(Module oldModule, Collection documentIds) { Map srcDocContextMap = new LinkedHashMap<>(); for (DocumentId documentId : documentIds) { @@ -335,8 +306,7 @@ private Module createNewModule(Map srcDocContextMap Set moduleContextSet = new HashSet<>(); ModuleContext newModuleContext = new ModuleContext(this.project, this.moduleId, this.moduleDescriptor, this.isDefaultModule, srcDocContextMap, - testDocContextMap, this.moduleMdContext, this.dependencies, this.resourceContextMap, - this.testResourceContextMap); + testDocContextMap, this.moduleMdContext, this.dependencies); moduleContextSet.add(newModuleContext); // add dependant modules including transitives @@ -348,8 +318,7 @@ private Module createNewModule(Map srcDocContextMap Modifier module = this.packageInstance.module(dependentDescriptor.name()).modify(); moduleContextSet.add(new ModuleContext(this.project, module.moduleId, dependentDescriptor, module.isDefaultModule, module.srcDocContextMap, - module.testDocContextMap, module.moduleMdContext, module.dependencies, - module.resourceContextMap, module.testResourceContextMap)); + module.testDocContextMap, module.moduleMdContext, module.dependencies)); } Package newPackage = this.packageInstance.modify().updateModules(moduleContextSet).apply(); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ModuleConfig.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ModuleConfig.java index a4b12d111bbe..2898d66560f2 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ModuleConfig.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ModuleConfig.java @@ -17,7 +17,6 @@ */ package io.ballerina.projects; -import java.util.Collections; import java.util.List; import java.util.Optional; @@ -34,25 +33,19 @@ public class ModuleConfig { private final List testSrcDocs; private final List dependencies; private final DocumentConfig moduleMd; - private final List resources; - private final List testResources; private ModuleConfig(ModuleId moduleId, ModuleDescriptor moduleDescriptor, List srcDocs, List testSrcDocs, DocumentConfig moduleMd, - List dependencies, - List resources, - List testResources) { + List dependencies) { this.moduleId = moduleId; this.moduleDescriptor = moduleDescriptor; this.srcDocs = srcDocs; this.testSrcDocs = testSrcDocs; this.dependencies = dependencies; this.moduleMd = moduleMd; - this.resources = resources; - this.testResources = testResources; } public static ModuleConfig from(ModuleId moduleId, @@ -61,8 +54,7 @@ public static ModuleConfig from(ModuleId moduleId, List testSrcDocs, DocumentConfig moduleMd, List dependencies) { - return new ModuleConfig(moduleId, moduleDescriptor, srcDocs, testSrcDocs, moduleMd, dependencies, - Collections.emptyList(), Collections.emptyList()); + return new ModuleConfig(moduleId, moduleDescriptor, srcDocs, testSrcDocs, moduleMd, dependencies); } public static ModuleConfig from(ModuleId moduleId, @@ -74,7 +66,7 @@ public static ModuleConfig from(ModuleId moduleId, List resources, List testResources) { return new ModuleConfig( - moduleId, moduleDescriptor, srcDocs, testSrcDocs, moduleMd, dependencies, resources, testResources); + moduleId, moduleDescriptor, srcDocs, testSrcDocs, moduleMd, dependencies); } public ModuleId moduleId() { @@ -105,11 +97,13 @@ public Optional moduleMd() { return Optional.ofNullable(this.moduleMd); } + @Deprecated(since = "2201.10.0", forRemoval = true) public List resources() { - return resources; + return null; } + @Deprecated(since = "2201.10.0", forRemoval = true) public List testResources() { - return testResources; + return null; } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ModuleContext.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ModuleContext.java index 4c5f3159c77f..7a43f6bf4b07 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ModuleContext.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/ModuleContext.java @@ -46,7 +46,6 @@ import java.io.IOException; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -74,10 +73,6 @@ class ModuleContext { private final Collection testSrcDocIds; private final MdDocumentContext moduleMdContext; private final Map testDocContextMap; - private final Collection resourceIds; - private final Collection testResourceIds; - private final Map resourceContextMap; - private final Map testResourceContextMap; private final Project project; private final CompilationCache compilationCache; private final List moduleDescDependencies; @@ -98,9 +93,7 @@ class ModuleContext { Map srcDocContextMap, Map testDocContextMap, MdDocumentContext moduleMd, - List moduleDescDependencies, - Map resourceContextMap, - Map testResourceContextMap) { + List moduleDescDependencies) { this.project = project; this.moduleId = moduleId; this.moduleDescriptor = moduleDescriptor; @@ -111,10 +104,7 @@ class ModuleContext { this.testSrcDocIds = Collections.unmodifiableCollection(testDocContextMap.keySet()); this.moduleMdContext = moduleMd; this.moduleDescDependencies = Collections.unmodifiableList(moduleDescDependencies); - this.resourceContextMap = resourceContextMap; - this.testResourceContextMap = testResourceContextMap; - this.resourceIds = Collections.unmodifiableCollection(resourceContextMap.keySet()); - this.testResourceIds = Collections.unmodifiableCollection(testResourceContextMap.keySet()); + ProjectEnvironment projectEnvironment = project.projectEnvironmentContext(); this.bootstrap = new Bootstrap(projectEnvironment.getService(PackageResolver.class)); @@ -134,20 +124,10 @@ static ModuleContext from(Project project, ModuleConfig moduleConfig, boolean di disableSyntaxTree)); } - Map resourceContextMap = new HashMap<>(); - for (ResourceConfig resourceConfig : moduleConfig.resources()) { - resourceContextMap.put(resourceConfig.documentId(), ResourceContext.from(resourceConfig)); - } - - Map testResourceContextMap = new HashMap<>(); - for (ResourceConfig resourceConfig : moduleConfig.testResources()) { - testResourceContextMap.put(resourceConfig.documentId(), ResourceContext.from(resourceConfig)); - } - return new ModuleContext(project, moduleConfig.moduleId(), moduleConfig.moduleDescriptor(), moduleConfig.isDefaultModule(), srcDocContextMap, testDocContextMap, moduleConfig.moduleMd().map(c ->MdDocumentContext.from(c)).orElse(null), - moduleConfig.dependencies(), resourceContextMap, testResourceContextMap); + moduleConfig.dependencies()); } ModuleId moduleId() { @@ -170,14 +150,6 @@ Collection testSrcDocumentIds() { return this.testSrcDocIds; } - Collection resourceIds() { - return this.resourceIds; - } - - Collection testResourceIds() { - return this.testResourceIds; - } - DocumentContext documentContext(DocumentId documentId) { if (this.srcDocIds.contains(documentId)) { return this.srcDocContextMap.get(documentId); @@ -186,14 +158,6 @@ DocumentContext documentContext(DocumentId documentId) { } } - ResourceContext resourceContext(DocumentId documentId) { - if (this.resourceIds.contains(documentId)) { - return this.resourceContextMap.get(documentId); - } else { - return this.testResourceContextMap.get(documentId); - } - } - Project project() { return this.project; } @@ -578,7 +542,7 @@ ModuleContext duplicate(Project project) { } return new ModuleContext(project, this.moduleId, this.moduleDescriptor, this.isDefaultModule, srcDocContextMap, testDocContextMap, this.moduleMdContext().orElse(null), - this.moduleDescDependencies, this.resourceContextMap, this.testResourceContextMap); + this.moduleDescDependencies); } /** diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/NullBackend.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/NullBackend.java index cbffe9dbd014..f20ab67a0489 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/NullBackend.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/NullBackend.java @@ -77,6 +77,11 @@ public PlatformLibrary codeGeneratedTestLibrary(PackageId packageId, ModuleName return null; } + @Override + public PlatformLibrary codeGeneratedResourcesLibrary(PackageId packageId) { + return null; + } + @Override public PlatformLibrary runtimeLibrary() { return null; diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Package.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Package.java index 0d59cd0fb03c..7611ca1e83a0 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Package.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Package.java @@ -43,6 +43,9 @@ public class Package { private Optional cloudToml = Optional.empty(); private Optional compilerPluginToml = Optional.empty(); private Optional balToolToml = Optional.empty(); + private final Map resources; + private final Map testResources; + private final Function populateResourceFunc; private Package(PackageContext packageContext, Project project) { this.packageContext = packageContext; @@ -50,6 +53,10 @@ private Package(PackageContext packageContext, Project project) { this.moduleMap = new ConcurrentHashMap<>(); this.populateModuleFunc = moduleId -> Module.from( this.packageContext.moduleContext(moduleId), this); + this.resources = new ConcurrentHashMap<>(); + this.testResources = new ConcurrentHashMap<>(); + this.populateResourceFunc = documentId -> new Resource( + this.packageContext.resourceContext(documentId), this); } static Package from(Project project, PackageConfig packageConfig, CompilationOptions compilationOptions) { @@ -228,6 +235,24 @@ public Optional packageMd() { return this.packageMd; } + public Collection resourceIds() { + return this.packageContext.resourceIds(); + } + + public Collection testResourceIds() { + return this.packageContext.testResourceIds(); + } + + public Resource resource(DocumentId documentId) { + // TODO Should we throw an error if the documentId is not present + if (resourceIds().contains(documentId)) { + return this.resources.computeIfAbsent(documentId, this.populateResourceFunc); + } else { + return this.testResources.computeIfAbsent(documentId, this.populateResourceFunc); + } + } + + Package duplicate(Project project) { return new Package(packageContext.duplicate(project), project); } @@ -425,6 +450,8 @@ public static class Modifier { private TomlDocumentContext compilerPluginTomlContext; private TomlDocumentContext balToolTomlContext; private MdDocumentContext packageMdContext; + private final Map resourceContextMap; + private final Map testResourceContextMap; public Modifier(Package oldPackage) { this.packageId = oldPackage.packageId(); @@ -440,6 +467,8 @@ public Modifier(Package oldPackage) { this.compilerPluginTomlContext = oldPackage.packageContext.compilerPluginTomlContext().orElse(null); this.balToolTomlContext = oldPackage.packageContext.balToolTomlContext().orElse(null); this.packageMdContext = oldPackage.packageContext.packageMdContext().orElse(null); + resourceContextMap = copyResources(oldPackage, oldPackage.packageContext.resourceIds()); + testResourceContextMap = copyResources(oldPackage, oldPackage.packageContext.testResourceIds()); } Modifier updateModules(Set newModuleContexts) { @@ -572,7 +601,6 @@ public Modifier removePackageMd() { } - Modifier updateBallerinaToml(BallerinaToml ballerinaToml) { this.ballerinaTomlContext = ballerinaToml.ballerinaTomlContext(); updatePackageManifest(); @@ -626,12 +654,13 @@ private Map copyModules(Package oldPackage) { private Package createNewPackage() { Package oldPackage = this.project.currentPackage(); - PackageResolution oldResolution = oldPackage.getResolution();; + PackageResolution oldResolution = oldPackage.getResolution(); PackageContext newPackageContext = new PackageContext(this.project, this.packageId, this.packageManifest, this.dependencyManifest, this.ballerinaTomlContext, this.dependenciesTomlContext, this.cloudTomlContext, this.compilerPluginTomlContext, this.balToolTomlContext, this.packageMdContext, this.compilationOptions, this.moduleContextMap, - DependencyGraph.emptyGraph()); + DependencyGraph.emptyGraph(), this.resourceContextMap, + this.testResourceContextMap); this.project.setCurrentPackage(new Package(newPackageContext, this.project)); if (isOldDependencyGraphValid(oldPackage, this.project.currentPackage())) { this.project.currentPackage().packageContext().getResolution(oldResolution); @@ -734,9 +763,9 @@ private void updatePackageManifest() { private void updateDependencyManifest() { DependencyManifestBuilder manifestBuilder = DependencyManifestBuilder.from( - Optional.ofNullable(this.dependenciesTomlContext) - .map(TomlDocumentContext::tomlDocument).orElse(null), - project.currentPackage().descriptor()); + Optional.ofNullable(this.dependenciesTomlContext) + .map(TomlDocumentContext::tomlDocument).orElse(null), + project.currentPackage().descriptor()); this.dependencyManifest = manifestBuilder.dependencyManifest(); } @@ -761,25 +790,64 @@ private void updateModules() { testDocContextMap.put(documentId, oldModuleContext.documentContext(documentId)); } - Map resourceMap = new HashMap<>(); - for (DocumentId documentId : oldModuleContext.resourceIds()) { - resourceMap.put(documentId, oldModuleContext.resourceContext(documentId)); - } - - Map testResourceMap = new HashMap<>(); - for (DocumentId documentId : oldModuleContext.testResourceIds()) { - testResourceMap.put(documentId, oldModuleContext.resourceContext(documentId)); - } - moduleContextSet.add(new ModuleContext(this.project, moduleId, moduleDescriptor, oldModuleContext.isDefaultModule(), srcDocContextMap, testDocContextMap, oldModuleContext.moduleMdContext().orElse(null), - oldModuleContext.moduleDescDependencies(), resourceMap, testResourceMap)); + oldModuleContext.moduleDescDependencies())); // Remove the module with old PackageID from the compilation cache PackageCache.getInstance(project.projectEnvironmentContext().getService(CompilerContext.class)). remove(oldModuleContext.descriptor().moduleCompilationId()); } updateModules(moduleContextSet); } + + private Map copyResources(Package oldPackage, Collection documentIds) { + Map resourceContextMap = new HashMap<>(); + for (DocumentId documentId : documentIds) { + resourceContextMap.put(documentId, oldPackage.packageContext.resourceContext(documentId)); + } + return resourceContextMap; + } + + /** + * Creates a copy of the existing module and adds a new resource to the new module. + * + * @param resourceConfig configurations to create the resource + * @return an instance of the Module.Modifier + */ + public Package.Modifier addResource(ResourceConfig resourceConfig) { + ResourceContext newResourceContext = ResourceContext.from(resourceConfig); + this.resourceContextMap.put(newResourceContext.documentId(), newResourceContext); + return this; + } + + /** + * Creates a copy of the existing module and adds a new test resource to the new module. + * + * @param resourceConfig configurations to create the test resource + * @return an instance of the Module.Modifier + */ + public Package.Modifier addTestResource(ResourceConfig resourceConfig) { + ResourceContext newResourceContext = ResourceContext.from(resourceConfig); + this.testResourceContextMap.put(newResourceContext.documentId(), newResourceContext); + return this; + } + + /** + * Creates a copy of the existing module and removes the specified resource from the new module. + * + * @param documentId documentId of the resource to remove + * @return an instance of the Module.Modifier + */ + public Package.Modifier removeResource(DocumentId documentId) { + + if (this.resourceContextMap.containsKey(documentId)) { + this.resourceContextMap.remove(documentId); + } else { + this.testResourceContextMap.remove(documentId); + } + return this; + } + } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageConfig.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageConfig.java index 7768172ecfeb..9ea7569e8574 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageConfig.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageConfig.java @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.util.Collection; +import java.util.List; import java.util.Optional; /** @@ -44,6 +45,8 @@ public class PackageConfig { private final Collection otherModules; private final DocumentConfig packageMd; private final boolean disableSyntaxTree; + private final List resources; + private final List testResources; private PackageConfig(PackageId packageId, Path packagePath, @@ -57,7 +60,9 @@ private PackageConfig(PackageId packageId, Collection moduleConfigs, DependencyGraph packageDescDependencyGraph, DocumentConfig packageMd, - boolean disableSyntaxTree) { + boolean disableSyntaxTree, + List resources, + List testResources) { this.packageId = packageId; this.packagePath = packagePath; this.packageManifest = packageManifest; @@ -71,22 +76,8 @@ private PackageConfig(PackageId packageId, this.packageDescDependencyGraph = packageDescDependencyGraph; this.packageMd = packageMd; this.disableSyntaxTree = disableSyntaxTree; - } - - public static PackageConfig from(PackageId packageId, - Path packagePath, - PackageManifest packageManifest, - DependencyManifest dependencyManifest, - DocumentConfig ballerinaToml, - DocumentConfig dependenciesToml, - DocumentConfig cloudToml, - DocumentConfig compilerPluginToml, - DocumentConfig balToolToml, - DocumentConfig packageMd, - Collection moduleConfigs) { - return new PackageConfig(packageId, packagePath, packageManifest, dependencyManifest, ballerinaToml, - dependenciesToml, cloudToml, compilerPluginToml, balToolToml, moduleConfigs, - DependencyGraph.emptyGraph(), packageMd, false); + this.resources = resources; + this.testResources = testResources; } public static PackageConfig from(PackageId packageId, @@ -100,10 +91,12 @@ public static PackageConfig from(PackageId packageId, DocumentConfig balToolToml, DocumentConfig packageMd, Collection moduleConfigs, - DependencyGraph packageDescDependencyGraph) { + List resources, + List testResources) { return new PackageConfig(packageId, packagePath, packageManifest, dependencyManifest, ballerinaToml, dependenciesToml, cloudToml, compilerPluginToml, balToolToml, moduleConfigs, - packageDescDependencyGraph, packageMd, false); + DependencyGraph.emptyGraph(), packageMd, false, resources, + testResources); } public static PackageConfig from(PackageId packageId, @@ -118,10 +111,26 @@ public static PackageConfig from(PackageId packageId, DocumentConfig packageMd, Collection moduleConfigs, DependencyGraph packageDescDependencyGraph, - boolean disableSyntaxTree) { + List resources, + List testResources) { return new PackageConfig(packageId, packagePath, packageManifest, dependencyManifest, ballerinaToml, + dependenciesToml, cloudToml, compilerPluginToml, balToolToml, moduleConfigs, + packageDescDependencyGraph, packageMd, false, resources, + testResources); + } + + public static PackageConfig from(PackageId packageId, Path path, PackageManifest packageManifest, + DependencyManifest dependencyManifest, DocumentConfig ballerinaToml, + DocumentConfig dependenciesToml, DocumentConfig cloudToml, + DocumentConfig compilerPluginToml, DocumentConfig balToolToml, + DocumentConfig packageMd, List moduleConfigs, + DependencyGraph packageDependencyGraph, + boolean disableSyntaxTree, List resources, + List testResources) { + return new PackageConfig(packageId, path, packageManifest, dependencyManifest, ballerinaToml, dependenciesToml, cloudToml, compilerPluginToml, balToolToml, moduleConfigs, - packageDescDependencyGraph, packageMd, disableSyntaxTree); + packageDependencyGraph, packageMd, disableSyntaxTree, resources, + testResources); } public PackageId packageId() { @@ -197,4 +206,12 @@ public Optional dependenciesToml() { boolean isSyntaxTreeDisabled() { return disableSyntaxTree; } + + public List resources() { + return resources; + } + + public List testResources() { + return testResources; + } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageContext.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageContext.java index 643c428ccef1..a646004e8ffd 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageContext.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageContext.java @@ -52,6 +52,10 @@ class PackageContext { private final CompilationOptions compilationOptions; private ModuleContext defaultModuleContext; + private final Collection resourceIds; + private final Collection testResourceIds; + private final Map resourceContextMap; + private final Map testResourceContextMap; /** * This variable holds the dependency graph cached in a project. @@ -80,7 +84,9 @@ class PackageContext { MdDocumentContext packageMdContext, CompilationOptions compilationOptions, Map moduleContextMap, - DependencyGraph pkgDescDependencyGraph) { + DependencyGraph pkgDescDependencyGraph, + Map resourceContextMap, + Map testResourceContextMap) { this.project = project; this.packageId = packageId; this.packageManifest = packageManifest; @@ -98,6 +104,10 @@ class PackageContext { this.moduleCompilationMap = new HashMap<>(); this.packageDependencies = Collections.emptySet(); this.pkgDescDependencyGraph = pkgDescDependencyGraph; + this.resourceContextMap = resourceContextMap; + this.testResourceContextMap = testResourceContextMap; + this.resourceIds = Collections.unmodifiableCollection(resourceContextMap.keySet()); + this.testResourceIds = Collections.unmodifiableCollection(testResourceContextMap.keySet()); } static PackageContext from(Project project, PackageConfig packageConfig, CompilationOptions compilationOptions) { @@ -106,7 +116,15 @@ static PackageContext from(Project project, PackageConfig packageConfig, Compila moduleContextMap.put(moduleConfig.moduleId(), ModuleContext.from(project, moduleConfig, packageConfig.isSyntaxTreeDisabled())); } + Map resourceContextMap = new HashMap<>(); + for (ResourceConfig resourceConfig : packageConfig.resources()) { + resourceContextMap.put(resourceConfig.documentId(), ResourceContext.from(resourceConfig)); + } + Map testResourceContextMap = new HashMap<>(); + for (ResourceConfig resourceConfig : packageConfig.testResources()) { + testResourceContextMap.put(resourceConfig.documentId(), ResourceContext.from(resourceConfig)); + } return new PackageContext(project, packageConfig.packageId(), packageConfig.packageManifest(), packageConfig.dependencyManifest(), packageConfig.ballerinaToml().map(TomlDocumentContext::from).orElse(null), @@ -115,7 +133,8 @@ static PackageContext from(Project project, PackageConfig packageConfig, Compila packageConfig.compilerPluginToml().map(TomlDocumentContext::from).orElse(null), packageConfig.balToolToml().map(TomlDocumentContext::from).orElse(null), packageConfig.packageMd().map(MdDocumentContext::from).orElse(null), - compilationOptions, moduleContextMap, packageConfig.packageDescDependencyGraph()); + compilationOptions, moduleContextMap, packageConfig.packageDescDependencyGraph(), + resourceContextMap, testResourceContextMap); } PackageId packageId() { @@ -327,6 +346,22 @@ private void resolveModuleDependencies(ModuleContext moduleContext, } } + Collection resourceIds() { + return this.resourceIds; + } + + Collection testResourceIds() { + return this.testResourceIds; + } + + ResourceContext resourceContext(DocumentId documentId) { + if (this.resourceIds.contains(documentId)) { + return this.resourceContextMap.get(documentId); + } else { + return this.testResourceContextMap.get(documentId); + } + } + PackageContext duplicate(Project project) { Map duplicatedModuleContextMap = new HashMap<>(); for (ModuleId moduleId : this.moduleIds) { @@ -337,6 +372,7 @@ PackageContext duplicate(Project project) { return new PackageContext(project, this.packageId, this.packageManifest, this.dependencyManifest, this.ballerinaTomlContext, this.dependenciesTomlContext, this.cloudTomlContext, this.compilerPluginTomlContext, this.balToolTomlContext, this.packageMdContext, - this.compilationOptions, duplicatedModuleContextMap, this.pkgDescDependencyGraph); + this.compilationOptions, duplicatedModuleContextMap, this.pkgDescDependencyGraph, + this.resourceContextMap, this.testResourceContextMap); } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java index 61401f13273d..90cb5f5f536f 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java @@ -49,6 +49,7 @@ import org.wso2.ballerinalang.compiler.util.Names; import org.wso2.ballerinalang.util.RepoUtils; +import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; @@ -59,7 +60,12 @@ import java.util.Optional; import java.util.stream.Collectors; +import static io.ballerina.projects.util.ProjectConstants.BALLERINA_HOME; import static io.ballerina.projects.util.ProjectConstants.DOT; +import static io.ballerina.projects.util.ProjectConstants.EQUAL; +import static io.ballerina.projects.util.ProjectConstants.OFFLINE_FLAG; +import static io.ballerina.projects.util.ProjectConstants.REPOSITORY_FLAG; +import static io.ballerina.projects.util.ProjectConstants.STICKY_FLAG; /** * Resolves dependencies and handles version conflicts in the dependency graph. @@ -97,6 +103,70 @@ private PackageResolution(PackageContext rootPackageContext, CompilationOptions DependencyResolution dependencyResolution = new DependencyResolution( projectEnvContext.getService(PackageCache.class), moduleResolver, dependencyGraph); resolveDependencies(dependencyResolution); + if (compilationOptions.optimizeDependencyCompilation()) { + generateCaches(); + } + } + + /** + * This method performs the BIR generation before the compilation by + * spinning up a new process. + * This is typically useful for large packages to avoid OOM issues. + */ + private void generateCaches() { + for (ResolvedPackageDependency resolvedPackageDependency : this.dependencyGraph.toTopologicallySortedList()) { + Package packageInstance = resolvedPackageDependency.packageInstance(); + + // If the package instance is the current package, we have reached the root of the dependency graph. + // We skip the generation of the cache for the current package. + PackageDescriptor packageDescriptor = packageInstance.descriptor(); + if (packageDescriptor == this.rootPackageContext.descriptor()) { + break; + } + + // If the dependency is not loaded from sources, then we assume that the BIR is already generated. + // We skip the cache generation for the particular dependency. + if (packageInstance.getDefaultModule().moduleContext() + .currentCompilationState() != ModuleCompilationState.LOADED_FROM_SOURCES) { + continue; + } + + // We use the pull command to generate the BIR of the dependency. + List cmdArgs = new ArrayList<>(); + cmdArgs.add(System.getProperty(BALLERINA_HOME) + "/bin/bal"); + cmdArgs.add("pull"); + cmdArgs.add(STICKY_FLAG + EQUAL + resolutionOptions.sticky()); + cmdArgs.add(OFFLINE_FLAG + EQUAL + resolutionOptions.offline()); + + // Specify which repository to resolve the dependency from + Optional dependency = + blendedManifest.userSpecifiedDependency(packageDescriptor.org(), packageDescriptor.name()); + if (dependency.isPresent() && dependency.get().repository() != null) { + cmdArgs.add(REPOSITORY_FLAG + EQUAL + dependency.get().repository()); + } + cmdArgs.add(packageDescriptor.toString()); + + ProcessBuilder processBuilder = new ProcessBuilder(cmdArgs); + try { + Process process = processBuilder.start(); + int i = process.waitFor(); + if (i != 0) { + String errMessage = packageDescriptor.toString(); + if (dependency.isPresent()) { + errMessage += " [repository=" + dependency.get().repository() + "]"; + } + throw new ProjectException("failed to compile " + errMessage); + } + } catch (IOException | InterruptedException e) { + throw new ProjectException(e); + } + + // Finally, we set the compilation state of the dependency to LOADED_FROM_CACHE + for (ModuleId moduleId : packageInstance.moduleIds()) { + packageInstance.module(moduleId).moduleContext() + .setCompilationState(ModuleCompilationState.LOADED_FROM_CACHE); + } + } } private PackageResolution(PackageResolution packageResolution, PackageContext rootPackageContext, @@ -233,7 +303,7 @@ public boolean autoUpdate() { */ private DependencyGraph buildDependencyGraph() { // TODO We should get diagnostics as well. Need to design that contract - if (rootPackageContext.project().kind() == ProjectKind.BALA_PROJECT) { + if (rootPackageContext.project().kind() == ProjectKind.BALA_PROJECT && this.resolutionOptions.sticky()) { return resolveBALADependencies(); } else { return resolveSourceDependencies(); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Project.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Project.java index 32807620ef96..adafba2c0f42 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Project.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Project.java @@ -88,6 +88,8 @@ public Path sourceRoot() { public abstract Path targetDir(); + public abstract Path generatedResourcesDir(); + protected void setCurrentPackage(Package currentPackage) { // TODO Handle concurrent read/write to the currentPackage variable this.currentPackage = currentPackage; diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Resource.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Resource.java index e520c3f0984c..289e69ebd72a 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Resource.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Resource.java @@ -25,16 +25,16 @@ public class Resource { private final ResourceContext resourceContext; - private final Module module; + private final Package packageInstance; - Resource(ResourceContext resourceContext, Module module) { + Resource(ResourceContext resourceContext, Package aPackage) { this.resourceContext = resourceContext; - this.module = module; + this.packageInstance = aPackage; } - Resource from(ResourceConfig resourceConfig, Module module) { + Resource from(ResourceConfig resourceConfig, Package aPackage) { ResourceContext resourceContext = ResourceContext.from(resourceConfig); - return new Resource(resourceContext, module); + return new Resource(resourceContext, aPackage); } public DocumentId documentId() { @@ -49,7 +49,12 @@ public byte[] content() { return resourceContext.content(); } + @Deprecated(since = "2201.10.0", forRemoval = true) public Module module() { - return module; + return packageInstance.getDefaultModule(); + } + + public Package packageInstance() { + return packageInstance; } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/bala/BalaProject.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/bala/BalaProject.java index 81febf8d9c61..282079a6df1c 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/bala/BalaProject.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/bala/BalaProject.java @@ -18,6 +18,7 @@ package io.ballerina.projects.bala; +import io.ballerina.projects.BuildOptions; import io.ballerina.projects.Document; import io.ballerina.projects.DocumentId; import io.ballerina.projects.Module; @@ -53,13 +54,21 @@ public class BalaProject extends Project { */ public static BalaProject loadProject(ProjectEnvironmentBuilder environmentBuilder, Path balaPath) { PackageConfig packageConfig = PackageConfigCreator.createBalaProjectConfig(balaPath); - BalaProject balaProject = new BalaProject(environmentBuilder, balaPath); + BalaProject balaProject = new BalaProject(environmentBuilder, balaPath, BuildOptions.builder().build()); balaProject.addPackage(packageConfig); return balaProject; } - private BalaProject(ProjectEnvironmentBuilder environmentBuilder, Path balaPath) { - super(ProjectKind.BALA_PROJECT, balaPath, environmentBuilder); + public static BalaProject loadProject(ProjectEnvironmentBuilder environmentBuilder, Path balaPath, + BuildOptions buildOptions) { + PackageConfig packageConfig = PackageConfigCreator.createBalaProjectConfig(balaPath); + BalaProject balaProject = new BalaProject(environmentBuilder, balaPath, buildOptions); + balaProject.addPackage(packageConfig); + return balaProject; + } + + private BalaProject(ProjectEnvironmentBuilder environmentBuilder, Path balaPath, BuildOptions buildOptions) { + super(ProjectKind.BALA_PROJECT, balaPath, environmentBuilder, buildOptions); this.platform = BalaFiles.readPackageJson(balaPath).getPlatform(); } @@ -75,7 +84,8 @@ public void clearCaches() { public Project duplicate() { ProjectEnvironmentBuilder projectEnvironmentBuilder = ProjectEnvironmentBuilder.getDefaultBuilder(); projectEnvironmentBuilder.addCompilationCacheFactory(TempDirCompilationCache::from); - BalaProject balaProject = new BalaProject(projectEnvironmentBuilder, this.sourceRoot); + BuildOptions duplicateBuildOptions = BuildOptions.builder().build().acceptTheirs(buildOptions()); + BalaProject balaProject = new BalaProject(projectEnvironmentBuilder, this.sourceRoot, duplicateBuildOptions); return resetPackage(balaProject); } @@ -139,4 +149,9 @@ private boolean isFilePathInProject(Path filepath) { public Path targetDir() { throw new UnsupportedOperationException("target directory is not supported for BalaProject"); } + + @Override + public Path generatedResourcesDir() { + throw new UnsupportedOperationException("generated resources directory is not supported for BalaProject"); + } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/BuildProject.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/BuildProject.java index 43157ae5ed29..371dbcfcc5eb 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/BuildProject.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/BuildProject.java @@ -254,7 +254,7 @@ public DocumentId documentId(Path file) { } } } - throw new ProjectException("provided path does not belong to the project"); + throw new ProjectException("'" + file.toString() + "' does not belong to the current project"); } private boolean isFilePathInProject(Path filepath) { @@ -526,4 +526,17 @@ public Path targetDir() { return Paths.get(this.buildOptions().getTargetPath()); } } + + @Override + public Path generatedResourcesDir() { + Path generatedResourcesPath = targetDir().resolve(ProjectConstants.RESOURCE_DIR_NAME); + if (!Files.exists(generatedResourcesPath)) { + try { + Files.createDirectories(generatedResourcesPath); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return generatedResourcesPath; + } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/ProjectLoader.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/ProjectLoader.java index a4744b81c66b..8ab9c19e13cc 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/ProjectLoader.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/ProjectLoader.java @@ -81,7 +81,7 @@ public static Project loadProject(Path path, ProjectEnvironmentBuilder projectEn return BuildProject.load(projectEnvironmentBuilder, projectRoot, buildOptions); } else if (Files.exists(projectRoot.resolve(ProjectConstants.PACKAGE_JSON))) { projectEnvironmentBuilder.addCompilationCacheFactory(TempDirCompilationCache::from); - return BalaProject.loadProject(projectEnvironmentBuilder, projectRoot); + return BalaProject.loadProject(projectEnvironmentBuilder, projectRoot, buildOptions); } else { throw new ProjectException("provided directory does not belong to any supported project types"); } @@ -92,7 +92,7 @@ public static Project loadProject(Path path, ProjectEnvironmentBuilder projectEn } if (!ProjectPaths.isBalFile(absFilePath)) { - throw new ProjectException("provided path is not a valid Ballerina source file"); + throw new ProjectException("'" + absFilePath + "' is not a valid Ballerina source file"); } try { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/SingleFileProject.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/SingleFileProject.java index 9b594e8e5caa..599d9afc9347 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/SingleFileProject.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/directory/SingleFileProject.java @@ -26,6 +26,7 @@ import io.ballerina.projects.ProjectKind; import io.ballerina.projects.internal.PackageConfigCreator; import io.ballerina.projects.repos.TempDirCompilationCache; +import io.ballerina.projects.util.ProjectConstants; import java.io.IOException; import java.nio.file.Files; @@ -51,7 +52,7 @@ public static SingleFileProject load(ProjectEnvironmentBuilder environmentBuilde } public static SingleFileProject load(ProjectEnvironmentBuilder environmentBuilder, Path filePath, - BuildOptions buildOptions) { + BuildOptions buildOptions) { PackageConfig packageConfig = PackageConfigCreator.createSingleFileProjectConfig(filePath); SingleFileProject singleFileProject = new SingleFileProject( environmentBuilder, filePath, buildOptions); @@ -104,7 +105,7 @@ public Project duplicate() { public DocumentId documentId(Path file) { if (!this.sourceRoot.toAbsolutePath().normalize().toString().equals( file.toAbsolutePath().normalize().toString())) { - throw new ProjectException("provided path does not belong to the project"); + throw new ProjectException("'" + file + "' does not belong to the current project"); } return this.currentPackage().getDefaultModule().documentIds().iterator().next(); } @@ -125,4 +126,17 @@ public void save() { public Path targetDir() { return this.targetDir; } + + @Override + public Path generatedResourcesDir() { + Path generatedResourcesPath = this.targetDir.resolve(ProjectConstants.RESOURCE_DIR_NAME); + if (!Files.exists(generatedResourcesPath)) { + try { + Files.createDirectories(generatedResourcesPath); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return generatedResourcesPath; + } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BalaFiles.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BalaFiles.java index 8a0abb8d2f53..9aa21e2b4f01 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BalaFiles.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BalaFiles.java @@ -105,7 +105,13 @@ private static PackageData loadPackageDataFromBalaDir(Path balaPath, PackageMani .resolve(ProjectConstants.PACKAGE_MD_FILE_NAME)); // load other modules List otherModules = loadOtherModules(pkgName, balaPath); - return PackageData.from(balaPath, defaultModule, otherModules, null, null, null, null, null, packageMd); + List resources = loadResources(balaPath); + if (resources.isEmpty()) { + // get resources from default module path - to support balas before 2201.10.0 + resources = loadResources(balaPath.resolve(MODULES_ROOT).resolve(pkgName)); + } + return PackageData.from(balaPath, defaultModule, otherModules, null, null, + null, null, null, packageMd, resources, Collections.emptyList()); } private static PackageData loadPackageDataFromBalaFile(Path balaPath, PackageManifest packageManifest) { @@ -119,7 +125,13 @@ private static PackageData loadPackageDataFromBalaFile(Path balaPath, PackageMan .resolve(ProjectConstants.PACKAGE_MD_FILE_NAME)); // load other modules List otherModules = loadOtherModules(pkgName, packageRoot); - return PackageData.from(balaPath, defaultModule, otherModules, null, null, null, null, null, packageMd); + List resources = loadResources(packageRoot); + if (resources.isEmpty()) { + // get resources from default module path - to support bala files before 2201.10.0 + resources = loadResources(packageRoot.resolve(MODULES_ROOT).resolve(pkgName)); + } + return PackageData.from(balaPath, defaultModule, otherModules, null, null, + null, null, null, packageMd, resources, Collections.emptyList()); } catch (IOException e) { throw new ProjectException("Failed to read bala file:" + balaPath); } @@ -151,7 +163,7 @@ public static DocumentData loadDocument(Path documentFilePath) { } } - private static ModuleData loadModule(String pkgName, String fullModuleName, Path packagePath) { + private static ModuleData loadModule(String pkgName, String fullModuleName, Path packagePath) { Path modulePath = packagePath.resolve(MODULES_ROOT).resolve(fullModuleName); Path moduleDocPath = packagePath.resolve(BALA_DOCS_DIR).resolve(MODULES_ROOT).resolve(fullModuleName); // check module path exists @@ -178,10 +190,8 @@ private static ModuleData loadModule(String pkgName, String fullModuleName, Pat List srcDocs = loadDocuments(modulePath); List testSrcDocs = Collections.emptyList(); DocumentData moduleMd = loadDocument(moduleDocPath.resolve(ProjectConstants.MODULE_MD_FILE_NAME)); - List resources = loadResources(modulePath); - return ModuleData.from(modulePath, moduleName, srcDocs, testSrcDocs, moduleMd, resources, - Collections.emptyList()); + return ModuleData.from(modulePath, moduleName, srcDocs, testSrcDocs, moduleMd); } private static List loadOtherModules(String pkgName, Path packagePath) { @@ -376,7 +386,7 @@ private static void extractPlatformLibraries(PackageJson packageJson, Path balaP } private static void extractCompilerPluginLibraries(CompilerPluginJson compilerPluginJson, Path balaPath, - FileSystem zipFileSystem) { + FileSystem zipFileSystem) { if (compilerPluginJson.dependencyPaths() == null) { return; } @@ -452,7 +462,8 @@ private static void setBalToolDependencyPaths(BalToolJson balToolJson, Path bala } private static PackageManifest getPackageManifest(PackageJson packageJson, - Optional compilerPluginJson, Optional balToolJson, String deprecationMsg) { + Optional compilerPluginJson, + Optional balToolJson, String deprecationMsg) { PackageDescriptor pkgDesc; if (deprecationMsg != null) { pkgDesc = PackageDescriptor.from(PackageOrg.from(packageJson.getOrganization()), @@ -518,20 +529,20 @@ private static DependencyManifest getDependencyManifest(DependencyGraphJson depe if (dependency.getModules() != null && !dependency.getModules().isEmpty()) { for (io.ballerina.projects.internal.model.Dependency.Module depModule : dependency.getModules()) { DependencyManifest.Module module = new DependencyManifest.Module(depModule.org(), - depModule.packageName(), - depModule.moduleName()); + depModule.packageName(), + depModule.moduleName()); modules.add(module); } } DependencyManifest.Package pkg = new DependencyManifest.Package(PackageName.from(dependency.getName()), - PackageOrg.from(dependency.getOrg()), - PackageVersion.from(dependency.getVersion()), - dependency.getScope() != null ? dependency.getScope().name() : null, - dependency.isTransitive(), - dependencies, - modules); + PackageOrg.from(dependency.getOrg()), + PackageVersion.from(dependency.getVersion()), + dependency.getScope() != null ? dependency.getScope().name() : null, + dependency.isTransitive(), + dependencies, + modules); packages.add(pkg); } return DependencyManifest.from(null, null, packages, Collections.emptyList()); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/CompilerPhaseRunner.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/CompilerPhaseRunner.java index 3163dfd71121..8e810141ba39 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/CompilerPhaseRunner.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/CompilerPhaseRunner.java @@ -19,7 +19,6 @@ import io.ballerina.runtime.internal.util.RuntimeUtils; import org.ballerinalang.compiler.CompilerPhase; -import org.wso2.ballerinalang.compiler.PackageCache; import org.wso2.ballerinalang.compiler.bir.BIRGen; import org.wso2.ballerinalang.compiler.bir.emit.BIREmitter; import org.wso2.ballerinalang.compiler.desugar.ConstantPropagation; @@ -33,7 +32,6 @@ import org.wso2.ballerinalang.compiler.semantics.analyzer.SemanticAnalyzer; import org.wso2.ballerinalang.compiler.semantics.analyzer.SymbolEnter; import org.wso2.ballerinalang.compiler.semantics.analyzer.SymbolResolver; -import org.wso2.ballerinalang.compiler.semantics.model.SymbolTable; import org.wso2.ballerinalang.compiler.tree.BLangPackage; import org.wso2.ballerinalang.compiler.util.CompilerContext; import org.wso2.ballerinalang.compiler.util.CompilerOptions; @@ -53,8 +51,6 @@ public class CompilerPhaseRunner { new CompilerContext.Key<>(); private final CompilerOptions options; - private final PackageCache pkgCache; - private final SymbolTable symbolTable; private final SymbolEnter symbolEnter; private final SymbolResolver symResolver; private final SemanticAnalyzer semAnalyzer; @@ -83,8 +79,6 @@ private CompilerPhaseRunner(CompilerContext context) { context.put(COMPILER_DRIVER_KEY, this); this.options = CompilerOptions.getInstance(context); - this.pkgCache = PackageCache.getInstance(context); - this.symbolTable = SymbolTable.getInstance(context); this.symbolEnter = SymbolEnter.getInstance(context); this.semAnalyzer = SemanticAnalyzer.getInstance(context); this.symResolver = SymbolResolver.getInstance(context); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ManifestBuilder.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ManifestBuilder.java index e7f9a6381355..0eb8542d253c 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ManifestBuilder.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ManifestBuilder.java @@ -699,6 +699,8 @@ private BuildOptions setBuildOptions(TomlTableNode tomlTableNode) { CompilerOptionName.REMOTE_MANAGEMENT.toString()); Boolean showDependencyDiagnostics = getBooleanFromBuildOptionsTableNode(tableNode, BuildOptions.OptionName.SHOW_DEPENDENCY_DIAGNOSTICS.toString()); + Boolean optimizeDependencyCompilation = getBooleanFromBuildOptionsTableNode(tableNode, + BuildOptions.OptionName.OPTIMIZE_DEPENDENCY_COMPILATION.toString()); buildOptionsBuilder .setOffline(offline) @@ -714,7 +716,8 @@ private BuildOptions setBuildOptions(TomlTableNode tomlTableNode) { .setExportComponentModel(exportComponentModel) .setGraalVMBuildOptions(graalVMBuildOptions) .setRemoteManagement(remoteManagement) - .setShowDependencyDiagnostics(showDependencyDiagnostics); + .setShowDependencyDiagnostics(showDependencyDiagnostics) + .setOptimizeDependencyCompilation(optimizeDependencyCompilation); if (targetDir != null) { buildOptionsBuilder.targetDir(targetDir); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ModuleData.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ModuleData.java index 5ed3283535be..4829807c6ffd 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ModuleData.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ModuleData.java @@ -33,8 +33,6 @@ public class ModuleData { private final List srcDocs; private final List testSrcDocs; private final DocumentData moduleMd; - private final List resources; - private final List testResources; // TODO do we need to maintain resources and test resources @@ -42,26 +40,20 @@ private ModuleData(Path moduleDirPath, String moduleName, List srcDocs, List testSrcDocs, - DocumentData moduleMd, - List resources, - List testResources) { + DocumentData moduleMd) { this.moduleDirPath = moduleDirPath; this.moduleName = moduleName; this.srcDocs = srcDocs; this.testSrcDocs = testSrcDocs; this.moduleMd = moduleMd; - this.resources = resources; - this.testResources = testResources; } public static ModuleData from(Path path, String moduleName, List srcDocuments, List testSrcDocuments, - DocumentData moduleMd, - List resources, - List testResources) { - return new ModuleData(path, moduleName, srcDocuments, testSrcDocuments, moduleMd, resources, testResources); + DocumentData moduleMd) { + return new ModuleData(path, moduleName, srcDocuments, testSrcDocuments, moduleMd); } public Path moduleDirectoryPath() { @@ -92,11 +84,4 @@ public Optional moduleMd() { return Optional.ofNullable(this.moduleMd); } - public List resources () { - return resources; - } - - public List testResources() { - return testResources; - } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageConfigCreator.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageConfigCreator.java index 956c726985bb..9c2350c54a6e 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageConfigCreator.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageConfigCreator.java @@ -38,11 +38,14 @@ import io.ballerina.projects.util.ProjectConstants; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; /** @@ -161,11 +164,18 @@ private static PackageConfig createPackageConfig(PackageData packageData, .map(data -> createDocumentConfig(data, null)).orElse(null); DocumentConfig packageMd = packageData.packageMd() .map(data -> createDocumentConfig(data, null)).orElse(null); - + List resources = new ArrayList<>(); + List testResources = new ArrayList<>(); + if (!packageData.resources().isEmpty()) { + resources = getResourceConfigs(packageData.resources(), packageData.packagePath()); + } + if (!packageData.testResources().isEmpty()) { + testResources = getResourceConfigs(packageData.testResources(), packageData.packagePath()); + } return PackageConfig .from(packageId, packageData.packagePath(), packageManifest, dependencyManifest, ballerinaToml, dependenciesToml, cloudToml, compilerPluginToml, balToolToml, packageMd, moduleConfigs, - packageDependencyGraph, disableSyntaxTree); + packageDependencyGraph, disableSyntaxTree, resources, testResources); } public static PackageConfig createPackageConfig(PackageData packageData, PackageManifest packageManifest, @@ -220,23 +230,19 @@ private static ModuleConfig createModuleConfig(ModuleDescriptor moduleDescriptor DocumentConfig moduleMd = moduleData.moduleMd() .map(data -> createDocumentConfig(data, null)).orElse(null); - List resources = getResourceConfigs( - moduleId, moduleData.resources(), moduleData.moduleDirectoryPath()); - List testResources = getResourceConfigs( - moduleId, moduleData.testResources(), moduleData.moduleDirectoryPath() - .resolve(ProjectConstants.TEST_DIR_NAME)); - return ModuleConfig.from(moduleId, moduleDescriptor, srcDocs, testSrcDocs, moduleMd, dependencies, resources, - testResources); + return ModuleConfig.from(moduleId, moduleDescriptor, srcDocs, testSrcDocs, moduleMd, dependencies); } - private static List getResourceConfigs(ModuleId moduleId, List resources, Path modulePath) { - return resources.stream().map(resource -> - createResourceConfig(resource, modulePath, moduleId)).collect(Collectors.toList()); + private static List getResourceConfigs(List resources, Path packagePath) { + // TODO: no need Remove duplicate paths before processing + Set distinctResources = new HashSet<>(resources); + return distinctResources.stream().map( + distinctResource -> createResourceConfig(distinctResource, packagePath)).collect(Collectors.toList()); } - private static ResourceConfig createResourceConfig(Path path, Path modulePath, ModuleId moduleId) { - final DocumentId documentId = DocumentId.create(path.toString(), moduleId); - return ProvidedResourceConfig.from(documentId, path, modulePath); + private static ResourceConfig createResourceConfig(Path path, Path packagePath) { + final DocumentId documentId = DocumentId.create(path.toString(), null); + return ProvidedResourceConfig.from(documentId, path, packagePath); } private static List getDocumentConfigs(ModuleId moduleId, List documentData) { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageData.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageData.java index 30ba2d1361bd..fd8be26e51de 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageData.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageData.java @@ -43,7 +43,8 @@ public class PackageData { private final DocumentData compilerPluginToml; private final DocumentData balToolToml; private final DocumentData packageMd; - + private final List resources; + private final List testResources; private PackageData(Path packagePath, ModuleData defaultModule, @@ -55,7 +56,9 @@ private PackageData(Path packagePath, DocumentData cloudToml, DocumentData compilerPluginToml, DocumentData balToolToml, - DocumentData packageMd) { + DocumentData packageMd, + List resources, + List testResources) { this.packagePath = packagePath; this.defaultModule = defaultModule; this.otherModules = otherModules; @@ -67,6 +70,8 @@ private PackageData(Path packagePath, this.cloudToml = cloudToml; this.compilerPluginToml = compilerPluginToml; this.balToolToml = balToolToml; + this.resources = resources; + this.testResources = testResources; } public static PackageData from(Path packagePath, @@ -77,10 +82,12 @@ public static PackageData from(Path packagePath, DocumentData cloudToml, DocumentData compilerPluginToml, DocumentData balToolToml, - DocumentData packageMd) { + DocumentData packageMd, + List resources, + List testResources) { return new PackageData(packagePath, defaultModule, otherModules, DependencyGraph.emptyGraph(), DependencyGraph.emptyGraph(), ballerinaToml, dependenciesToml, cloudToml, - compilerPluginToml, balToolToml, packageMd); + compilerPluginToml, balToolToml, packageMd, resources, testResources); } public static PackageData from(Path packagePath, @@ -93,10 +100,12 @@ public static PackageData from(Path packagePath, DocumentData cloudToml, DocumentData compilerPluginToml, DocumentData balToolToml, - DocumentData packageMd) { + DocumentData packageMd, + List resources, + List testResources) { return new PackageData(packagePath, defaultModule, otherModules, packageDesDependencyGraph, moduleDependencyGraph, ballerinaToml, dependenciesToml, cloudToml, compilerPluginToml, - balToolToml, packageMd); + balToolToml, packageMd, resources, testResources); } public Path packagePath() { @@ -142,4 +151,12 @@ public Optional balToolToml() { public Optional packageMd() { return Optional.ofNullable(packageMd); } + + public List resources () { + return resources; + } + + public List testResources() { + return testResources; + } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProjectDiagnosticErrorCode.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProjectDiagnosticErrorCode.java index 00dc9bc826de..63d6483cf6a9 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProjectDiagnosticErrorCode.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProjectDiagnosticErrorCode.java @@ -62,8 +62,8 @@ public enum ProjectDiagnosticErrorCode implements DiagnosticCode { CONFLICTING_PLATFORM_JAR_FILES("BCE5501", "conflicting.platform.jars.type"), PROVIDED_PLATFORM_JAR_IN_EXECUTABLE("BCE5502", "provided.platform.jars"), - // Error codes used for pack command - DEPRECATED_DOC_FILE("BCE5601", "deprecated.doc.file"), + // Error codes used in resources resolution + CONFLICTING_RESOURCE_FILE("BCE5601", "conflicting.resources.type") ; private final String diagnosticId; diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProjectFiles.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProjectFiles.java index 7f6c4506fbaf..595d5a22c3a5 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProjectFiles.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProjectFiles.java @@ -28,6 +28,7 @@ import java.io.File; import java.io.IOException; +import java.io.PrintStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; @@ -42,6 +43,8 @@ import java.util.stream.Stream; import static io.ballerina.projects.util.ProjectConstants.DOT; +import static io.ballerina.projects.util.ProjectConstants.GENERATED_MODULES_ROOT; +import static io.ballerina.projects.util.ProjectConstants.RESOURCE_DIR_NAME; import static io.ballerina.projects.util.ProjectConstants.TEST_DIR_NAME; import static io.ballerina.projects.util.ProjectUtils.checkReadPermission; @@ -55,6 +58,7 @@ public class ProjectFiles { FileSystems.getDefault().getPathMatcher("glob:**.bal"); public static final PathMatcher BALA_EXTENSION_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.bala"); + private static final PrintStream outStream = System.out; private ProjectFiles() { } @@ -62,10 +66,10 @@ private ProjectFiles() { public static PackageData loadSingleFileProjectPackageData(Path filePath) { DocumentData documentData = loadDocument(filePath); ModuleData defaultModule = ModuleData - .from(filePath, DOT, Collections.singletonList(documentData), Collections.emptyList(), null, - Collections.emptyList(), Collections.emptyList()); + .from(filePath, DOT, Collections.singletonList(documentData), Collections.emptyList(), null); return PackageData.from(filePath, defaultModule, Collections.emptyList(), - null, null, null, null, null, null); + null, null, null, null, + null, null, Collections.emptyList(), Collections.emptyList()); } public static PackageData loadBuildProjectPackageData(Path packageDirPath) { @@ -83,9 +87,21 @@ public static PackageData loadBuildProjectPackageData(Path packageDirPath) { DocumentData compilerPluginToml = loadDocument(packageDirPath.resolve(ProjectConstants.COMPILER_PLUGIN_TOML)); DocumentData balToolToml = loadDocument(packageDirPath.resolve(ProjectConstants.BAL_TOOL_TOML)); DocumentData packageMd = loadDocument(packageDirPath.resolve(ProjectConstants.PACKAGE_MD_FILE_NAME)); - + List resources = loadResources(packageDirPath); + // load generated resources + List generatedResources = loadResources(packageDirPath.resolve(GENERATED_MODULES_ROOT)); + if (!generatedResources.isEmpty()) { + resources.addAll(generatedResources); + } + List testResources = loadResources(packageDirPath.resolve(ProjectConstants.TEST_DIR_NAME)); + // load generated test resources + List generatedTestResources = loadResources(packageDirPath.resolve( + GENERATED_MODULES_ROOT).resolve(TEST_DIR_NAME)); + if (!generatedTestResources.isEmpty()) { + testResources.addAll(generatedTestResources); + } return PackageData.from(packageDirPath, defaultModule, otherModules, ballerinaToml, dependenciesToml, - cloudToml, compilerPluginToml, balToolToml, packageMd); + cloudToml, compilerPluginToml, balToolToml, packageMd, resources, testResources); } private static List loadNewGeneratedModules(Path packageDirPath) { @@ -119,7 +135,7 @@ private static List loadNewGeneratedModules(Path packageDirPath) { private static boolean isNewModule(Path packageDirPath, Path path) { String dirName = path.toFile().getName(); - if (dirName.equals(TEST_DIR_NAME)) { + if (dirName.equals(TEST_DIR_NAME) || dirName.equals(RESOURCE_DIR_NAME)) { return false; } Path modulePath = packageDirPath.resolve(ProjectConstants.MODULES_ROOT).resolve(dirName); @@ -164,15 +180,8 @@ private static ModuleData loadModule(Path moduleDirPath) { // If the module is not a newly generated module, explicitly load generated sources if (!ProjectConstants.GENERATED_MODULES_ROOT.equals(Optional.of( moduleDirPath.toAbsolutePath().getParent()).get().toFile().getName())) { - // Generated sources root for default module - Path generatedSourcesRoot = moduleDirPath.resolve(ProjectConstants.GENERATED_MODULES_ROOT); - if (ProjectConstants.MODULES_ROOT.equals(Optional.of( - moduleDirPath.toAbsolutePath().getParent()).get().toFile().getName())) { - // generated sources root for non-default modules - generatedSourcesRoot = Optional.of(Optional.of(Optional.of(moduleDirPath.toAbsolutePath().getParent()). - get().getParent()).get().resolve(ProjectConstants.GENERATED_MODULES_ROOT)) - .get().resolve(Optional.of(moduleDirPath.toFile()).get().getName()); - } + // Generated source root for default module + Path generatedSourcesRoot = getGeneratedSourcesRoot(moduleDirPath); if (Files.isDirectory(generatedSourcesRoot)) { List generatedDocs = loadDocuments(generatedSourcesRoot); verifyDuplicateNames(srcDocs, generatedDocs, moduleDirPath.toFile().getName(), moduleDirPath, false); @@ -187,11 +196,36 @@ private static ModuleData loadModule(Path moduleDirPath) { } } DocumentData moduleMd = loadDocument(moduleDirPath.resolve(ProjectConstants.MODULE_MD_FILE_NAME)); - List resources = loadResources(moduleDirPath); - List testResources = loadResources(moduleDirPath.resolve(ProjectConstants.TEST_DIR_NAME)); + + // Warn if there are module level resources + Path parentPath = moduleDirPath.getParent(); + if (parentPath != null && ProjectConstants.MODULES_ROOT.equals( + parentPath.toFile().getName())) { + List moduleResources = loadResources(moduleDirPath); + if (!moduleResources.isEmpty()) { + String diagnosticMsg = "WARNING: module-level resources are not supported. Relocate the module-level " + + "resources detected in '" + moduleDirPath.toFile().getName() + "' to the package " + + "resources path. Resource files:\n" + + moduleResources.stream() + .map(Path::toString) + .collect(Collectors.joining("\n")) + "\n"; + ProjectUtils.addProjectLoadingDiagnostic(diagnosticMsg); + } + } // TODO Read Module.md file. Do we need to? Bala creator may need to package Module.md - return ModuleData.from(moduleDirPath, moduleDirPath.toFile().getName(), srcDocs, testSrcDocs, moduleMd, - resources, testResources); + return ModuleData.from(moduleDirPath, moduleDirPath.toFile().getName(), srcDocs, testSrcDocs, moduleMd); + } + + private static Path getGeneratedSourcesRoot(Path moduleDirPath) { + Path generatedSourcesRoot = moduleDirPath.resolve(ProjectConstants.GENERATED_MODULES_ROOT); + if (ProjectConstants.MODULES_ROOT.equals(Optional.of( + moduleDirPath.toAbsolutePath().getParent()).get().toFile().getName())) { + // generated sources root for non-default modules + generatedSourcesRoot = Optional.of(Optional.of(Optional.of(moduleDirPath.toAbsolutePath().getParent()). + get().getParent()).get().resolve(ProjectConstants.GENERATED_MODULES_ROOT)) + .get().resolve(Optional.of(moduleDirPath.toFile()).get().getName()); + } + return generatedSourcesRoot; } private static void verifyDuplicateNames(List srcDocs, List generatedDocs, @@ -213,14 +247,13 @@ private static void verifyDuplicateNames(List srcDocs, List loadResources(Path modulePath) { - Path resourcesPath = modulePath.resolve(ProjectConstants.RESOURCE_DIR_NAME); + public static List loadResources(Path packagePath) { + Path resourcesPath = packagePath.resolve(ProjectConstants.RESOURCE_DIR_NAME); if (Files.notExists(resourcesPath)) { return Collections.emptyList(); } - try { - checkReadPermission(modulePath); + checkReadPermission(packagePath); } catch (UnsupportedOperationException ignore) { // ignore for zip entries } @@ -334,7 +367,7 @@ public static void validateBuildProjectDirPath(Path projectDirPath) { } if (ProjectUtils.findProjectRoot(projectDirPath.toAbsolutePath().getParent()) != null) { - throw new ProjectException("Provided path is already within a Ballerina package: " + projectDirPath); + throw new ProjectException("'" + projectDirPath + "' is already within a Ballerina package"); } checkReadPermission(projectDirPath); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProvidedResourceConfig.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProvidedResourceConfig.java index 2fcd4817be4b..bf499d3df630 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProvidedResourceConfig.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ProvidedResourceConfig.java @@ -21,9 +21,9 @@ import io.ballerina.projects.DocumentId; import io.ballerina.projects.ResourceConfig; import io.ballerina.projects.util.ProjectConstants; -import org.apache.commons.io.FilenameUtils; import java.nio.file.Path; +import java.util.Locale; /** * {@code Resource} contains necessary configuration elements required to create an instance of a {@code Resource}. @@ -37,13 +37,33 @@ */ public class ProvidedResourceConfig extends ResourceConfig { + public static final String OS = System.getProperty("os.name").toLowerCase(Locale.getDefault()); + private ProvidedResourceConfig(DocumentId documentId, Path path, String name, byte[] content) { super(documentId, path, name, content); } - public static ProvidedResourceConfig from(DocumentId documentId, Path resource, Path modulePath) { - Path resourcePath = modulePath.resolve(ProjectConstants.RESOURCE_DIR_NAME).relativize(resource); - String unixPath = FilenameUtils.separatorsToUnix(resourcePath.toString()); - return new ProvidedResourceConfig(documentId, resource, unixPath, null); + public static ProvidedResourceConfig from(DocumentId documentId, Path resource, Path packagePath) { + Path relativeResourcePath; + if (resource.startsWith(packagePath)) { + relativeResourcePath = packagePath.relativize(resource); + } else { + relativeResourcePath = resource; + } + String resourcePath = relativeResourcePath.toString(); + String marker = ProjectConstants.RESOURCE_DIR_NAME + (OS.contains("win") ? "\\" : "/"); + int markerIndex = resourcePath.indexOf(marker); + String path = markerIndex != -1 + ? resourcePath.substring(markerIndex + marker.length()) + : resourcePath; + return new ProvidedResourceConfig(documentId, resource, convertWinPathToUnixFormat(path), null); + } + + public static String convertWinPathToUnixFormat(String path) { + if (OS.contains("win")) { + path = path.replace("\\", "/"); + } + return path; } + } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/jballerina/JarWriter.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/jballerina/JarWriter.java deleted file mode 100644 index bceecc59c5cd..000000000000 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/jballerina/JarWriter.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package io.ballerina.projects.internal.jballerina; - -import org.apache.commons.compress.archivers.jar.JarArchiveEntry; -import org.wso2.ballerinalang.compiler.CompiledJarFile; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Map; -import java.util.jar.Attributes; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; - -/** - * Write jar binary content to target path. - * - * @since 2.0.0 - */ -public class JarWriter { - - public static ByteArrayOutputStream write(CompiledJarFile compiledJarFile, Map resources) - throws IOException { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - writeJar(compiledJarFile, byteArrayOutputStream, resources); - return byteArrayOutputStream; - } - - private static Manifest getManifest(CompiledJarFile compiledJarFile) { - Manifest manifest = new Manifest(); - Attributes mainAttributes = manifest.getMainAttributes(); - mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); - compiledJarFile.getMainClassName().ifPresent(mainClassName -> - mainAttributes.put(Attributes.Name.MAIN_CLASS, mainClassName)); - return manifest; - } - - private static void writeJar(CompiledJarFile compiledJarFile, OutputStream outputStream, - Map resources) throws IOException { - Manifest manifest = getManifest(compiledJarFile); - try (JarOutputStream target = new JarOutputStream(outputStream, manifest)) { - Map jarEntries = compiledJarFile.getJarEntries(); - for (Map.Entry keyVal : jarEntries.entrySet()) { - byte[] entryContent = keyVal.getValue(); - JarEntry entry = new JarEntry(keyVal.getKey()); - target.putNextEntry(entry); - target.write(entryContent); - target.closeEntry(); - } - - // Copy resources - for (Map.Entry entry : resources.entrySet()) { - JarArchiveEntry e = new JarArchiveEntry(entry.getKey()); - target.putNextEntry(e); - target.write(entry.getValue()); - target.closeEntry(); - } - } - } -} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/model/Target.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/model/Target.java index db2848211ef3..61a9ba1bbc60 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/model/Target.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/model/Target.java @@ -46,6 +46,7 @@ public class Target { private Path nativePath; private Path nativeConfigPath; private Path profilerPath; + private Path resourcesPath; public Target(Path targetPath) throws IOException { this.targetPath = targetPath; @@ -60,6 +61,7 @@ public Target(Path targetPath) throws IOException { this.nativePath = this.targetPath.resolve(ProjectConstants.NATIVE_DIR_NAME); this.nativeConfigPath = this.testsCachePath.resolve(ProjectConstants.NATIVE_CONFIG_DIR_NAME); this.profilerPath = this.targetPath.resolve(ProjectConstants.PROFILER_DIR_NAME); + this.resourcesPath = this.targetPath.resolve(ProjectConstants.RESOURCE_DIR_NAME); if (Files.exists(this.targetPath)) { ProjectUtils.checkWritePermission(this.targetPath); @@ -86,6 +88,9 @@ public Target(Path targetPath) throws IOException { if (Files.exists(this.profilerPath)) { ProjectUtils.checkWritePermission(this.profilerPath); } + if (Files.exists(this.resourcesPath)) { + ProjectUtils.checkWritePermission(this.resourcesPath); + } } /** @@ -248,6 +253,7 @@ public void clean(boolean isModified, boolean cacheEnabled) throws IOException { ProjectUtils.deleteDirectory(this.binPath); ProjectUtils.deleteDirectory(this.docPath); ProjectUtils.deleteDirectory(this.reportPath); + ProjectUtils.deleteDirectory(this.resourcesPath); } /** diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/plugins/SourceGeneratorContext.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/plugins/SourceGeneratorContext.java index ba98b9ff0875..9f93d4da56b3 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/plugins/SourceGeneratorContext.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/plugins/SourceGeneratorContext.java @@ -84,6 +84,7 @@ public interface SourceGeneratorContext { * @param content a {@code TextDocument} that contains the source code * @param fileName proposed prefix of the filename to be added */ + @Deprecated(since = "2201.10.0", forRemoval = true) void addResourceFile(byte[] content, String fileName, ModuleId moduleId); /** @@ -92,6 +93,7 @@ public interface SourceGeneratorContext { * @param content a {@code TextDocument} that contains the source code * @param fileName proposed prefix of the filename to be added */ + @Deprecated(since = "2201.10.0", forRemoval = true) void addResourceFile(byte[] content, String fileName); /** @@ -100,6 +102,7 @@ public interface SourceGeneratorContext { * @param content a {@code TextDocument} that contains the test resource content * @param fileName proposed prefix of the filename to be added */ + @Deprecated(since = "2201.10.0", forRemoval = true) void addTestResourceFile(byte[] content, String fileName, ModuleId moduleId); /** @@ -108,6 +111,7 @@ public interface SourceGeneratorContext { * @param content a {@code TextDocument} that contains the test resource content * @param fileName proposed prefix of the filename to be added */ + @Deprecated(since = "2201.10.0", forRemoval = true) void addTestResourceFile(byte[] content, String fileName); /** diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java index bfe93c432246..a27d5e3f88d2 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java @@ -28,6 +28,7 @@ private ProjectConstants() {} public static final String BLANG_SOURCE_EXT = ".bal"; public static final String BALA_EXTENSION = ".bala"; public static final String PLATFORM = "platform"; + public static final String TOML_EXTENSION = ".toml"; public static final String BALLERINA_TOML = "Ballerina.toml"; public static final String DEPENDENCIES_TOML = "Dependencies.toml"; @@ -42,7 +43,6 @@ private ProjectConstants() {} public static final String DEVCONTAINER = ".devcontainer.json"; public static final String MODULE_MD_FILE_NAME = "Module.md"; public static final String PACKAGE_MD_FILE_NAME = "Package.md"; - public static final String README_MD_FILE_NAME = "README.md"; public static final String PACKAGE_JSON = "package.json"; public static final String BALA_JSON = "bala.json"; public static final String COMPILER_PLUGIN_JSON = "compiler-plugin.json"; @@ -99,6 +99,7 @@ private ProjectConstants() {} public static final String ANON_ORG = "$anon"; public static final String DOT = "."; + public static final String EQUAL = "="; public static final String DEFAULT_VERSION = "0.0.0"; public static final String INTERNAL_VERSION = "0.1.0"; public static final String MODULE_NAME_SEPARATOR = DOT; @@ -113,7 +114,7 @@ private ProjectConstants() {} public static final String TEST_RUNTIME_JAR_PREFIX = "testerina-runtime-"; public static final String TEST_CORE_JAR_PREFIX = "testerina-core-"; public static final String TEST_UBER_JAR_SUFFIX = "-testable"; - public static final String FAT_JAR_ROOT_DIR = "/"; + public static final String DIR_PATH_SEPARATOR = "/"; public static final String TEST_RUNTIME_MAIN_ARGS_FILE = "mainArgs.txt"; public static final String EXCLUDED_CLASSES_FILE = "excludedClasses.txt"; @@ -144,4 +145,8 @@ private ProjectConstants() {} public static final String ORG = "org"; public static final String PACKAGE_NAME = "name"; public static final String LOCAL_TOOLS_JSON = "local-tools.json"; + public static final String STICKY_FLAG = "--sticky"; + public static final String OFFLINE_FLAG = "--offline"; + public static final String REPOSITORY_FLAG = "--repository"; + public static final String WILD_CARD = "*"; } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectPaths.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectPaths.java index ece202a30f84..885c3c33939e 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectPaths.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectPaths.java @@ -45,7 +45,7 @@ public class ProjectPaths { public static Path packageRoot(Path filepath) throws ProjectException { // check if the file exists if (!Files.exists(filepath)) { - throw new ProjectException("provided path does not exist:" + filepath); + throw new ProjectException("'" + filepath + "'" + " does not exist"); } if (Files.isDirectory(filepath)) { @@ -62,7 +62,7 @@ public static Path packageRoot(Path filepath) throws ProjectException { // check if the file is a regular file if (!Files.isRegularFile(filepath)) { - throw new ProjectException("provided path is not a regular file: " + filepath); + throw new ProjectException("'" + filepath + "'" + " is not a regular file"); } // Check if the file is inside a Ballerina package directory @@ -79,7 +79,7 @@ public static Path packageRoot(Path filepath) throws ProjectException { } if (!isBalFile(filepath)) { - throw new ProjectException("provided path is not a valid Ballerina source file: " + filepath); + throw new ProjectException("'" + filepath + "' is not a valid Ballerina source file"); } // check if the file is a source file in the default module diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java index 147845d18f18..b135f8b27a76 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java @@ -24,6 +24,7 @@ import io.ballerina.compiler.syntax.tree.ImportDeclarationNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.SeparatedNodeList; +import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.projects.Document; import io.ballerina.projects.DocumentId; import io.ballerina.projects.JarLibrary; @@ -56,7 +57,6 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.archivers.zip.ZipFile; import org.ballerinalang.compiler.BLangCompilerException; -import org.wso2.ballerinalang.compiler.CompiledJarFile; import org.wso2.ballerinalang.compiler.util.Names; import org.wso2.ballerinalang.util.Lists; import org.wso2.ballerinalang.util.RepoUtils; @@ -71,6 +71,7 @@ import java.net.InetSocketAddress; import java.net.Proxy; import java.nio.charset.StandardCharsets; +import java.nio.file.DirectoryStream; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; @@ -88,7 +89,6 @@ import java.util.Set; import java.util.StringJoiner; import java.util.jar.Attributes; -import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.regex.Matcher; @@ -110,14 +110,18 @@ import static io.ballerina.projects.util.ProjectConstants.BUILD_FILE; import static io.ballerina.projects.util.ProjectConstants.CACHES_DIR_NAME; import static io.ballerina.projects.util.ProjectConstants.DIFF_UTILS_JAR; +import static io.ballerina.projects.util.ProjectConstants.DIR_PATH_SEPARATOR; +import static io.ballerina.projects.util.ProjectConstants.DOT; import static io.ballerina.projects.util.ProjectConstants.JACOCO_CORE_JAR; import static io.ballerina.projects.util.ProjectConstants.JACOCO_REPORT_JAR; import static io.ballerina.projects.util.ProjectConstants.LIB_DIR; +import static io.ballerina.projects.util.ProjectConstants.RESOURCE_DIR_NAME; import static io.ballerina.projects.util.ProjectConstants.TARGET_DIR_NAME; import static io.ballerina.projects.util.ProjectConstants.TEST_CORE_JAR_PREFIX; import static io.ballerina.projects.util.ProjectConstants.TEST_RUNTIME_JAR_PREFIX; import static io.ballerina.projects.util.ProjectConstants.TOOL_DIR; import static io.ballerina.projects.util.ProjectConstants.USER_NAME; +import static io.ballerina.projects.util.ProjectConstants.WILD_CARD; /** * Project related util methods. @@ -131,6 +135,7 @@ public class ProjectUtils { private static final Pattern onlyNonAlphanumericPattern = Pattern.compile("^[^a-zA-Z0-9]+$"); private static final Pattern orgNamePattern = Pattern.compile("^[a-zA-Z0-9_]*$"); private static final Pattern separatedIdentifierWithHyphenPattern = Pattern.compile("^[a-zA-Z0-9_.-]*$"); + private static String projectLoadingDiagnostic; /** * Validates the org-name. @@ -547,43 +552,6 @@ public static Path generateObservabilitySymbolsJar(String packageName) throws IO return jarPath; } - public static void assembleExecutableJar(Manifest manifest, - List compiledPackageJarList, - Path targetPath) throws IOException { - - // Used to prevent adding duplicated entries during the final jar creation. - HashSet copiedEntries = new HashSet<>(); - - try (ZipArchiveOutputStream outStream = new ZipArchiveOutputStream( - new BufferedOutputStream(new FileOutputStream(targetPath.toString())))) { - copyRuntimeJar(outStream, getBallerinaRTJarPath(), copiedEntries); - - JarArchiveEntry e = new JarArchiveEntry(JarFile.MANIFEST_NAME); - outStream.putArchiveEntry(e); - manifest.write(new BufferedOutputStream(outStream)); - outStream.closeArchiveEntry(); - - for (CompiledJarFile compiledJarFile : compiledPackageJarList) { - for (Map.Entry keyVal : compiledJarFile.getJarEntries().entrySet()) { - copyEntry(copiedEntries, outStream, keyVal); - } - } - } - } - - private static void copyEntry(HashSet copiedEntries, - ZipArchiveOutputStream outStream, - Map.Entry keyVal) throws IOException { - String entryName = keyVal.getKey(); - if (!isCopiedOrExcludedEntry(entryName, copiedEntries)) { - byte[] entryContent = keyVal.getValue(); - JarArchiveEntry entry = new JarArchiveEntry(entryName); - outStream.putArchiveEntry(entry); - outStream.write(entryContent); - outStream.closeArchiveEntry(); - } - } - /** * Copies a given jar file into the executable fat jar. * @@ -1409,6 +1377,44 @@ public static CompatibleRange getCompatibleRange(SemanticVersion version, Packag return CompatibleRange.LOCK_MAJOR; } + public static Map getAllGeneratedResources(Path generatedResourcesPath) { + Map resourcesMap = new HashMap<>(); + if (Files.isDirectory(generatedResourcesPath)) { + try (DirectoryStream stream = Files.newDirectoryStream( + generatedResourcesPath, Files::isRegularFile)) { + for (Path entry : stream) { + Path entryName = entry.getFileName(); + if (entryName == null) { + continue; + } + String resourcePath = RESOURCE_DIR_NAME + DIR_PATH_SEPARATOR + entryName.toString(); + resourcesMap.put(resourcePath, Files.readAllBytes(entry)); + } + } catch (IOException e) { + throw new ProjectException("An error occurred while reading the cached resources from: " + + generatedResourcesPath, e); + } + } + + return resourcesMap; + } + + public static String getConflictingResourcesMsg(String packageDesc, List conflictingResourceFiles) { + StringBuilder errorMessage = new StringBuilder(); + errorMessage.append("failed due to generated resources conflicting with the " + + "resources in the current package '").append(packageDesc).append("'. Conflicting resource files:"); + if (conflictingResourceFiles != null && !conflictingResourceFiles.isEmpty()) { + for (String file : conflictingResourceFiles) { + errorMessage.append("\n").append(file); + } + } + return errorMessage.toString(); + } + + public static String getResourcesPath() { + return "'" + RESOURCE_DIR_NAME + DIR_PATH_SEPARATOR + + DOT + WILD_CARD + "'"; + } /** * Denote the compatibility range of a given tool version. */ @@ -1430,4 +1436,34 @@ public enum CompatibleRange { */ EXACT } + + // TODO: Remove this with https://github.com/ballerina-platform/ballerina-lang/issues/43212 + // once diagnostic support for project loading stage is added. + public static void addProjectLoadingDiagnostic(String diagnosticMessage) { + projectLoadingDiagnostic = diagnosticMessage; + } + + public static String getProjectLoadingDiagnostic() { + return projectLoadingDiagnostic; + } + + /** + * Checks if there are any services in the default module of the project. + * + * @param pkg package instance + * @return true if there are services in the default module, false otherwise + */ + public static boolean containsDefaultModuleService(Package pkg) { + // Here, we are looking at the services only in the default module, since they are run during a bal run. + // However, we can extend this to look at other services + // (including within dependencies) that get engaged during run. + Module defaultModule = pkg.getDefaultModule(); + for (DocumentId documentId: pkg.getDefaultModule().documentIds()) { + ModulePartNode rootNode = defaultModule.document(documentId).syntaxTree().rootNode(); + if (rootNode.members().stream().anyMatch(member -> member.kind() == SyntaxKind.SERVICE_DECLARATION)) { + return true; + } + } + return false; + } } diff --git a/compiler/ballerina-lang/src/main/java/module-info.java b/compiler/ballerina-lang/src/main/java/module-info.java index 5c8060c52680..f3ae030b12a9 100644 --- a/compiler/ballerina-lang/src/main/java/module-info.java +++ b/compiler/ballerina-lang/src/main/java/module-info.java @@ -15,6 +15,7 @@ requires io.ballerina.central.client; requires io.ballerina.identifier; requires java.semver; + requires maven.resolver; exports io.ballerina.compiler.api; exports io.ballerina.compiler.api.symbols; exports io.ballerina.compiler.api.symbols.resourcepath; diff --git a/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java b/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java index 67c05042cf31..04024079b891 100644 --- a/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java +++ b/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java @@ -251,7 +251,6 @@ public enum DiagnosticErrorCode implements DiagnosticCode { INCOMPATIBLE_TYPES_CONVERSION_WITH_SUGGESTION("BCE2503", "incompatible.types.conversion.with.suggestion"), UNSAFE_CAST_ATTEMPT("BCE2504", "unsafe.cast.attempt"), - INVALID_LITERAL_FOR_TYPE("BCE2506", "invalid.literal.for.type"), INCOMPATIBLE_MAPPING_CONSTRUCTOR("BCE2507", "incompatible.mapping.constructor.expression"), MAPPING_CONSTRUCTOR_COMPATIBLE_TYPE_NOT_FOUND("BCE2508", "mapping.constructor.compatible.type.not.found"), CANNOT_INFER_TYPES_FOR_TUPLE_BINDING("BCE2509", "cannot.infer.types.for.tuple.binding"), @@ -333,7 +332,6 @@ public enum DiagnosticErrorCode implements DiagnosticCode { INVALID_ANY_VAR_DEF("BCE2574", "invalid.any.var.def"), INVALID_RECORD_LITERAL("BCE2575", "invalid.record.literal"), INVALID_FIELD_IN_RECORD_BINDING_PATTERN("BCE2576", "invalid.field.in.record.binding.pattern"), - INVALID_RECORD_LITERAL_BINDING_PATTERN("BCE2577", "invalid.record.literal.in.binding.pattern"), DUPLICATE_KEY_IN_MAPPING_CONSTRUCTOR("BCE2578", "duplicate.key.in.mapping.constructor"), DUPLICATE_KEY_IN_TABLE_LITERAL("BCE2579", "duplicate.key.in.table.literal"), DUPLICATE_KEY_IN_RECORD_LITERAL_SPREAD_OP("BCE2580", "duplicate.key.in.record.literal.spread.op"), @@ -819,6 +817,7 @@ public enum DiagnosticErrorCode implements DiagnosticCode { EXPRESSION_OF_FUTURE_TYPE_EXPECTED("BCE4057", "future.expression.expected"), INSTANTIATION_ERROR("BCE4058", "instantiation.error"), INVALID_BINDING_PATTERN_IN_ON_FAIL("BCE4059", "invalid.binding.pattern.in.on.fail"), + INVALID_USAGE_OF_CHECK_IN_PARAMETER_DEFAULT("BCE4060", "invalid.usage.of.check.in.parameter.default") ; private final String diagnosticId; diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/CodeGenerator.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/CodeGenerator.java index d52d4b9e801f..e1865531acf6 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/CodeGenerator.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/CodeGenerator.java @@ -17,7 +17,6 @@ */ package org.wso2.ballerinalang.compiler.bir.codegen; -import org.wso2.ballerinalang.compiler.CompiledJarFile; import org.wso2.ballerinalang.compiler.PackageCache; import org.wso2.ballerinalang.compiler.bir.BIRGenUtils; import org.wso2.ballerinalang.compiler.bir.model.BIRNode; diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/CompiledJarFile.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/CompiledJarFile.java similarity index 57% rename from compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/CompiledJarFile.java rename to compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/CompiledJarFile.java index 8656dc599e1c..a22730df10c6 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/CompiledJarFile.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/CompiledJarFile.java @@ -15,10 +15,10 @@ * specific language governing permissions and limitations * under the License. */ -package org.wso2.ballerinalang.compiler; +package org.wso2.ballerinalang.compiler.bir.codegen; -import java.util.Map; -import java.util.Optional; +import java.io.ByteArrayOutputStream; +import java.io.IOException; /** * A wrapper class for keeping code generated binary content and metadata of a program jar file. @@ -27,27 +27,13 @@ */ public class CompiledJarFile { - private String mainClassName; - private Map jarEntries; + public JarEntries jarEntries; - public CompiledJarFile(Map jarEntries) { - - this.jarEntries = jarEntries; - } - - public CompiledJarFile(String mainClassName, Map jarEntries) { - - this.mainClassName = mainClassName; - this.jarEntries = jarEntries; + public CompiledJarFile(String mainClassName) { + this.jarEntries = new JarEntries(mainClassName); } - public Map getJarEntries() { - - return jarEntries; - } - - public Optional getMainClassName() { - - return Optional.ofNullable(mainClassName); + public ByteArrayOutputStream toByteArrayStream() throws IOException { + return jarEntries.getByteArrayOutputStream(); } } diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JarEntries.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JarEntries.java new file mode 100644 index 000000000000..9ef98a319eb9 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JarEntries.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.ballerinalang.compiler.bir.codegen; + +import io.ballerina.projects.ProjectException; +import org.apache.commons.compress.archivers.jar.JarArchiveEntry; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +/** + * This class holds .class files as entries of a program JAR file and converts them into a ByteArrayOutputStream. + * + * @since 2201.10.0 + */ +public class JarEntries { + private final ByteArrayOutputStream byteArrayOutputStream; + private final JarOutputStream entries; + + protected JarEntries(String mainClassName) { + this.byteArrayOutputStream = new ByteArrayOutputStream(); + try { + entries = new JarOutputStream(this.byteArrayOutputStream, getManifest(mainClassName)); + } catch (IOException e) { + throw new ProjectException("Failed to create the JarOutputStream to cache jar entries", e); + } + } + + private static Manifest getManifest(String mainClassName) { + Manifest manifest = new Manifest(); + Attributes mainAttributes = manifest.getMainAttributes(); + mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + mainAttributes.put(Attributes.Name.MAIN_CLASS, mainClassName); + return manifest; + } + + public void put(String key, byte[] value) { + JarEntry entry = new JarEntry(key); + try { + entries.putNextEntry(entry); + entries.write(value); + entries.closeEntry(); + } catch (IOException e) { + throw new ProjectException("Failed to put the jar entry", e); + } + } + + public void putResourceEntries(Map resources) { + try { + for (Map.Entry entry : resources.entrySet()) { + JarArchiveEntry e = new JarArchiveEntry(entry.getKey()); + entries.putNextEntry(e); + entries.write(entry.getValue()); + entries.closeEntry(); + } + } catch (IOException e) { + throw new ProjectException("Failed to put the resource entries", e); + } + } + + protected ByteArrayOutputStream getByteArrayOutputStream() throws IOException { + entries.close(); + return byteArrayOutputStream; + } +} diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmCodeGenUtil.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmCodeGenUtil.java index e61675cbe8fe..2b3344d2627d 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmCodeGenUtil.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmCodeGenUtil.java @@ -198,8 +198,14 @@ public static String cleanupPathSeparators(String name) { return name.replace(WINDOWS_PATH_SEPERATOR, JAVA_PACKAGE_SEPERATOR); } - public static String rewriteVirtualCallTypeName(String value) { - return Utils.encodeFunctionIdentifier(cleanupObjectTypeName(value)); + public static String rewriteVirtualCallTypeName(String value, BType objectType) { + objectType = getImpliedType(objectType); + // The call name will be in the format of`objectTypeName.funcName` for attached functions of imported modules. + // Therefore, We need to remove the type name. + if (!objectType.tsymbol.name.value.isEmpty() && value.startsWith(objectType.tsymbol.name.value)) { + value = value.replace(objectType.tsymbol.name.value + ".", "").trim(); + } + return Utils.encodeFunctionIdentifier(value); } public static boolean isModuleInitializerMethod(String methodName) { @@ -423,22 +429,6 @@ public static String generateReturnType(BType bType) { }; } - static String cleanupObjectTypeName(String typeName) { - int index = typeName.lastIndexOf("."); // Internal type names can contain dots hence use the `lastIndexOf` - int typeNameLength = typeName.length(); - if (index > 1 && typeName.charAt(index - 1) == '\\') { // Methods can contain escaped characters - return typeName; - } else if (index > 0 && index != typeNameLength - 1) { // Resource method name can contain . at the end - return typeName.substring(index + 1); - } else if (index > 0) { - // We will reach here for resource methods eg: (MyClient8.$get$.) - index = typeName.substring(0, typeNameLength - 1).lastIndexOf("."); // Index of the . before the last . - return typeName.substring(index + 1); - } - - return typeName; - } - public static void loadChannelDetails(MethodVisitor mv, List channels, int invocationVarIndex) { mv.visitIntInsn(BIPUSH, channels.size()); diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmPackageGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmPackageGen.java index d82e37860490..f6316475a157 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmPackageGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmPackageGen.java @@ -28,7 +28,6 @@ import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodTooLargeException; import org.objectweb.asm.MethodVisitor; -import org.wso2.ballerinalang.compiler.CompiledJarFile; import org.wso2.ballerinalang.compiler.PackageCache; import org.wso2.ballerinalang.compiler.bir.codegen.exceptions.JInteropException; import org.wso2.ballerinalang.compiler.bir.codegen.internal.AsyncDataCollector; @@ -371,7 +370,7 @@ private static BIRFunction findFunction(List functions, String func return null; } - private void generateModuleClasses(BIRPackage module, Map jarEntries, String moduleInitClass, + private void generateModuleClasses(BIRPackage module, JarEntries jarEntries, String moduleInitClass, String typesClass, JvmTypeGen jvmTypeGen, JvmCastGen jvmCastGen, JvmConstantsGen jvmConstantsGen, Map jvmClassMapping, boolean serviceEPAvailable, BIRFunction mainFunc, BIRFunction testExecuteFunc, @@ -712,9 +711,10 @@ CompiledJarFile generate(BIRPackage module) { String typesClass = getModuleLevelClassName(module.packageID, MODULE_TYPES_CLASS_NAME); Map jvmClassMapping = generateClassNameLinking(module, moduleInitClass, true); - // use a map to store class byte values - final Map jarEntries = new HashMap<>(); - + CompiledJarFile compiledJarFile = new CompiledJarFile( + getModuleLevelClassName(module.packageID, MODULE_INIT_CLASS_NAME, ".")); + // use a ByteArrayOutputStream to store class byte values + final JarEntries jarEntries = compiledJarFile.jarEntries; // desugar parameter initialization injectDefaultParamInits(module, initMethodGen); injectDefaultParamInitsToAttachedFuncs(module, initMethodGen); @@ -773,7 +773,7 @@ CompiledJarFile generate(BIRPackage module) { // clear class name mappings clearPackageGenInfo(); - return new CompiledJarFile(getModuleLevelClassName(module.packageID, MODULE_INIT_CLASS_NAME, "."), jarEntries); + return compiledJarFile; } private void removeSourceAnnotationTypeDefs(List typeDefs) { diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmTerminatorGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmTerminatorGen.java index 1b033239aea5..22ed371eb9b1 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmTerminatorGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmTerminatorGen.java @@ -841,7 +841,7 @@ private void genVirtualCall(BIRTerminator.Call callIns, int localVarOffset) { this.mv.visitVarInsn(ALOAD, localVarOffset); // load the function name as the second argument - this.mv.visitLdcInsn(JvmCodeGenUtil.rewriteVirtualCallTypeName(callIns.name.value)); + this.mv.visitLdcInsn(JvmCodeGenUtil.rewriteVirtualCallTypeName(callIns.name.value, selfArg.type)); // create an Object[] for the rest params int argsCount = callIns.args.size() - 1; this.mv.visitLdcInsn((long) (argsCount)); diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmValueGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmValueGen.java index 19d6954f8851..4fbaa635a1e2 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmValueGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/JvmValueGen.java @@ -182,7 +182,7 @@ public static boolean isOptionalRecordField(BField field) { return (field.symbol.flags & BAL_OPTIONAL) == BAL_OPTIONAL; } - void generateValueClasses(Map jarEntries, JvmConstantsGen jvmConstantsGen, JvmTypeGen jvmTypeGen, + void generateValueClasses(JarEntries jarEntries, JvmConstantsGen jvmConstantsGen, JvmTypeGen jvmTypeGen, AsyncDataCollector asyncDataCollector) { String packageName = JvmCodeGenUtil.getPackageName(module.packageID); module.typeDefs.forEach(optionalTypeDef -> { @@ -455,7 +455,7 @@ private void createRecordPopulateInitialValuesMethod(ClassWriter cw, String clas private void createObjectValueClasses(BObjectType objectType, String className, BIRNode.BIRTypeDefinition typeDef, JvmConstantsGen jvmConstantsGen, AsyncDataCollector asyncDataCollector, - Map jarEntries) { + JarEntries jarEntries) { ClassWriter cw = new BallerinaClassWriter(COMPUTE_FRAMES); cw.visitSource(typeDef.pos.lineRange().fileName(), null); @@ -514,7 +514,7 @@ private void createObjectMethodsWithSplitClasses(ClassWriter cw, List jarEntries) { + JarEntries jarEntries) { int splitClassNum = 1; ClassWriter splitCW = new BallerinaClassWriter(COMPUTE_FRAMES); splitCW.visitSource(typeDef.pos.lineRange().fileName(), null); diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/ShutDownListenerGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/ShutDownListenerGen.java index 4dfbf629fd3e..440ca2ee2b2c 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/ShutDownListenerGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/ShutDownListenerGen.java @@ -22,8 +22,6 @@ import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; -import java.util.Map; - import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; @@ -51,7 +49,7 @@ */ public class ShutDownListenerGen { - void generateShutdownSignalListener(String initClass, Map jarEntries) { + void generateShutdownSignalListener(String initClass, JarEntries jarEntries) { String innerClassName = initClass + "$SignalListener"; ClassWriter cw = new BallerinaClassWriter(COMPUTE_FRAMES); cw.visit(V17, ACC_SUPER, innerClassName, null, JAVA_THREAD, null); diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/ConfigMethodGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/ConfigMethodGen.java index 3f26e0d40ade..1618d381a41d 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/ConfigMethodGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/ConfigMethodGen.java @@ -27,6 +27,7 @@ import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.wso2.ballerinalang.compiler.bir.codegen.BallerinaClassWriter; +import org.wso2.ballerinalang.compiler.bir.codegen.JarEntries; import org.wso2.ballerinalang.compiler.bir.codegen.JvmCodeGenUtil; import org.wso2.ballerinalang.compiler.bir.codegen.JvmConstants; import org.wso2.ballerinalang.compiler.bir.codegen.JvmTypeGen; @@ -38,7 +39,6 @@ import org.wso2.ballerinalang.util.Flags; import java.util.List; -import java.util.Map; import java.util.Set; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; @@ -96,7 +96,7 @@ public class ConfigMethodGen { public void generateConfigMapper(Set imprtMods, BIRNode.BIRPackage pkg, String moduleInitClass, JvmConstantsGen jvmConstantsGen, TypeHashVisitor typeHashVisitor, - Map jarEntries, SymbolTable symbolTable) { + JarEntries jarEntries, SymbolTable symbolTable) { innerClassName = JvmCodeGenUtil.getModuleLevelClassName(pkg.packageID, CONFIGURATION_CLASS_NAME); ClassWriter cw = new BallerinaClassWriter(COMPUTE_FRAMES); cw.visit(V17, ACC_PUBLIC | ACC_SUPER, innerClassName, null, OBJECT, null); diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/FrameClassGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/FrameClassGen.java index 7bae2948244c..3a116ac0efba 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/FrameClassGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/FrameClassGen.java @@ -23,13 +23,13 @@ import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Opcodes; import org.wso2.ballerinalang.compiler.bir.codegen.BallerinaClassWriter; +import org.wso2.ballerinalang.compiler.bir.codegen.JarEntries; import org.wso2.ballerinalang.compiler.bir.codegen.JvmCodeGenUtil; import org.wso2.ballerinalang.compiler.bir.model.BIRNode; import org.wso2.ballerinalang.compiler.semantics.model.types.BType; import org.wso2.ballerinalang.compiler.util.TypeTags; import java.util.List; -import java.util.Map; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; import static org.objectweb.asm.Opcodes.ACC_SUPER; @@ -46,7 +46,7 @@ */ public class FrameClassGen { - public void generateFrameClasses(BIRNode.BIRPackage pkg, Map pkgEntries) { + public void generateFrameClasses(BIRNode.BIRPackage pkg, JarEntries pkgEntries) { pkg.functions.forEach( func -> generateFrameClassForFunction(pkg.packageID, func, pkgEntries, null)); @@ -70,7 +70,7 @@ public void generateFrameClasses(BIRNode.BIRPackage pkg, Map pkg } private void generateFrameClassForFunction(PackageID packageID, BIRNode.BIRFunction func, - Map pkgEntries, + JarEntries pkgEntries, BType attachedType) { String frameClassName = MethodGenUtils.getFrameClassName(JvmCodeGenUtil.getPackageName(packageID), func.name.value, attachedType); diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/LambdaGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/LambdaGen.java index ed5f25013063..db6a0d634707 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/LambdaGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/LambdaGen.java @@ -26,6 +26,7 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.wso2.ballerinalang.compiler.bir.codegen.BallerinaClassWriter; +import org.wso2.ballerinalang.compiler.bir.codegen.JarEntries; import org.wso2.ballerinalang.compiler.bir.codegen.JvmCastGen; import org.wso2.ballerinalang.compiler.bir.codegen.JvmCodeGenUtil; import org.wso2.ballerinalang.compiler.bir.codegen.JvmConstants; @@ -118,7 +119,7 @@ public LambdaGen(JvmPackageGen jvmPackageGen, JvmCastGen jvmCastGen, BIRNode.BIR } public void generateLambdaClasses(AsyncDataCollector asyncDataCollector, - Map jarEntries) { + JarEntries jarEntries) { Map lambdaClasses = asyncDataCollector.getLambdaClasses(); if (lambdaClasses.isEmpty()) { return; @@ -227,7 +228,7 @@ private void genLoadDataForObjectAttachedLambdas(BIRTerminator.AsyncCall ins, Me mv.visitInsn(AALOAD); mv.visitTypeInsn(CHECKCAST, STRAND_CLASS); - mv.visitLdcInsn(JvmCodeGenUtil.rewriteVirtualCallTypeName(ins.name.value)); + mv.visitLdcInsn(JvmCodeGenUtil.rewriteVirtualCallTypeName(ins.name.value, ref.variableDcl.type)); int objectArrayLength = paramTypes.size() - 1; mv.visitIntInsn(BIPUSH, objectArrayLength); mv.visitTypeInsn(ANEWARRAY, OBJECT); diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/MainMethodGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/MainMethodGen.java index cd27923ed8c3..e4bd8a182f6c 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/MainMethodGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/MainMethodGen.java @@ -142,8 +142,8 @@ public class MainMethodGen { private final BIRVarToJVMIndexMap indexMap; private final JvmTypeGen jvmTypeGen; private final AsyncDataCollector asyncDataCollector; - private final boolean isRemoteMgtEnabled; private final String strandMetadataClass; + private final boolean isRemoteMgtEnabled; public MainMethodGen(SymbolTable symbolTable, JvmTypeGen jvmTypeGen, JvmConstantsGen jvmConstantsGen, AsyncDataCollector asyncDataCollector, boolean isRemoteMgtEnabled) { @@ -152,8 +152,8 @@ public MainMethodGen(SymbolTable symbolTable, JvmTypeGen jvmTypeGen, JvmConstant indexMap = new BIRVarToJVMIndexMap(1); this.jvmTypeGen = jvmTypeGen; this.asyncDataCollector = asyncDataCollector; - this.isRemoteMgtEnabled = isRemoteMgtEnabled; this.strandMetadataClass = jvmConstantsGen.getStrandMetadataConstantsClass(); + this.isRemoteMgtEnabled = isRemoteMgtEnabled; } public void generateMainMethod(BIRNode.BIRFunction userMainFunc, ClassWriter cw, BIRNode.BIRPackage pkg, diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmAnnotationsGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmAnnotationsGen.java index ecc62c6409a1..cde5f6a01cab 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmAnnotationsGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmAnnotationsGen.java @@ -22,6 +22,7 @@ import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.wso2.ballerinalang.compiler.bir.codegen.BallerinaClassWriter; +import org.wso2.ballerinalang.compiler.bir.codegen.JarEntries; import org.wso2.ballerinalang.compiler.bir.codegen.JvmCodeGenUtil; import org.wso2.ballerinalang.compiler.bir.codegen.JvmPackageGen; import org.wso2.ballerinalang.compiler.bir.codegen.JvmSignatures; @@ -32,7 +33,6 @@ import org.wso2.ballerinalang.util.Flags; import java.util.List; -import java.util.Map; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; @@ -71,7 +71,7 @@ public JvmAnnotationsGen(BIRNode.BIRPackage module, JvmPackageGen jvmPackageGen, this.module = module; } - public void generateAnnotationsClass(Map jarEntries) { + public void generateAnnotationsClass(JarEntries jarEntries) { ClassWriter cw = new BallerinaClassWriter(COMPUTE_FRAMES); cw.visit(V17, ACC_PUBLIC + ACC_SUPER, annotationsClass, null, OBJECT, null); generateProcessAnnotationsMethod(cw, module.typeDefs, module.packageID); diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmConstantsGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmConstantsGen.java index b6716009d88e..2d3856f11e4f 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmConstantsGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmConstantsGen.java @@ -19,6 +19,7 @@ import org.ballerinalang.model.elements.PackageID; import org.objectweb.asm.MethodVisitor; +import org.wso2.ballerinalang.compiler.bir.codegen.JarEntries; import org.wso2.ballerinalang.compiler.bir.codegen.internal.BTypeHashComparator; import org.wso2.ballerinalang.compiler.bir.codegen.internal.ScheduleFunctionInfo; import org.wso2.ballerinalang.compiler.bir.codegen.split.constants.JvmArrayTypeConstantsGen; @@ -101,7 +102,7 @@ public void setJvmCreateTypeGen(JvmCreateTypeGen jvmCreateTypeGen) { refTypeConstantsGen.setJvmRefTypeGen(jvmCreateTypeGen.getJvmRefTypeGen()); } - public void generateConstants(Map jarEntries, Map strandMetadata) { + public void generateConstants(JarEntries jarEntries, Map strandMetadata) { jvmBallerinaConstantsGen.generateConstantInit(jarEntries); unionTypeConstantsGen.generateClass(jarEntries); errorTypeConstantsGen.generateClass(jarEntries); diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmCreateTypeGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmCreateTypeGen.java index 0741b00c26cc..dd383de6ed93 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmCreateTypeGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/split/JvmCreateTypeGen.java @@ -23,6 +23,7 @@ import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.wso2.ballerinalang.compiler.bir.codegen.BallerinaClassWriter; +import org.wso2.ballerinalang.compiler.bir.codegen.JarEntries; import org.wso2.ballerinalang.compiler.bir.codegen.JvmCodeGenUtil; import org.wso2.ballerinalang.compiler.bir.codegen.JvmPackageGen; import org.wso2.ballerinalang.compiler.bir.codegen.JvmSignatures; @@ -173,7 +174,7 @@ public JvmCreateTypeGen(JvmTypeGen jvmTypeGen, JvmConstantsGen jvmConstantsGen, } public void generateTypeClass(JvmPackageGen jvmPackageGen, BIRNode.BIRPackage module, - Map jarEntries, + JarEntries jarEntries, String moduleInitClass, SymbolTable symbolTable) { generateCreateTypesMethod(typesCw, module.typeDefs, moduleInitClass, symbolTable); typesCw.visitEnd(); @@ -486,7 +487,7 @@ public static List