diff --git a/.taskcluster.yml b/.taskcluster.yml index c11609851e..94bb1aaafd 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -40,11 +40,6 @@ tasks: $if: 'tasks_for == "github-pull-request"' then: ${event.pull_request.head.repo.html_url} else: ${event.repository.html_url} - - clone_url: - $if: 'tasks_for == "github-pull-request"' - then: ${event.pull_request.head.repo.clone_url} - else: ${event.repository.clone_url} in: $let: default_task_definition: @@ -75,6 +70,8 @@ tasks: metadata: owner: &task_owner ${user}@users.noreply.github.com source: &task_source ${repository}/raw/${head_rev}/.taskcluster.yml + extra: + tasks_for: ${tasks_for} payload: artifacts: public/task-graph.json: @@ -101,13 +98,13 @@ tasks: python3 -m pip install pyyaml && git init repo && cd repo && - git fetch --tags ${clone_url} ${head_branch} && + git fetch --tags ${repository} ${head_branch} && git reset --hard ${head_rev} && python3 automation/taskcluster/decision_task.py env: - GIT_URL: ${clone_url} - GIT_REF: ${head_branch} - GIT_SHA: ${head_rev} + APPSERVICES_HEAD_REPOSITORY: ${repository} + APPSERVICES_HEAD_BRANCH: ${head_branch} + APPSERVICES_HEAD_REV: ${head_rev} TASK_FOR: ${tasks_for} TASK_OWNER: *task_owner TASK_SOURCE: *task_source @@ -137,18 +134,32 @@ tasks: description: Schedules the build and test tasks for Application Services. "tasks_for == 'github-release'": $let: + beetmover_worker_type: appsv-beetmover-v1 + beetmover_bucket: maven-production + beetmover_bucket_public_url: https://maven.mozilla.org/ tag: ${event.release.tag_name} release_task_definition: + payload: + features: + chainOfTrust: true scopes: - # So that we can publish to nalexander@'s personal bintray - # at https://bintray.com/ncalexander/application-services. - - "secrets:get:project/application-services/publish" + # So that we can publish on Maven using beetmover + - project:mozilla:application-services:releng:beetmover:action:push-to-maven # So that we can upload symbols to Socorro - "secrets:get:project/application-services/symbols-token" in: $mergeDeep: - {$eval: 'default_task_definition'} - {$eval: 'release_task_definition'} + - payload: + env: + BEETMOVER_WORKER_TYPE: ${beetmover_worker_type} + BEETMOVER_BUCKET: ${beetmover_bucket} + BEETMOVER_BUCKET_PUBLIC_URL: ${beetmover_bucket_public_url} + - scopes: + # So that we can publish on Maven using beetmover + - project:mozilla:application-services:releng:beetmover:bucket:${beetmover_bucket} + - queue:create-task:${tasks_priority}:scriptworker-prov-v1/${beetmover_worker_type} - metadata: name: Application Services - Decision task (${tag}) description: Build and publish release versions. diff --git a/automation/symbols-generation/symbolstore.py b/automation/symbols-generation/symbolstore.py index 445e1b9d7e..7eba377dfa 100644 --- a/automation/symbols-generation/symbolstore.py +++ b/automation/symbols-generation/symbolstore.py @@ -121,8 +121,8 @@ class GitHubRepoInfo: """ def __init__(self, path): self.path = path - if 'GIT_URL' in os.environ: - remote_url = os.environ['GIT_URL'] + if 'APPSERVICES_HEAD_REPOSITORY' in os.environ: + remote_url = os.environ['APPSERVICES_HEAD_REPOSITORY'] else: remote_url = read_output('git', '-C', path, 'remote', 'get-url', 'origin') match = githubRegex.match(remote_url) diff --git a/automation/taskcluster/decision_task.py b/automation/taskcluster/decision_task.py index 439d00ca62..d6874ad0ea 100644 --- a/automation/taskcluster/decision_task.py +++ b/automation/taskcluster/decision_task.py @@ -55,10 +55,11 @@ def main(task_for): # Calls "$PLATFORM_libs" functions and returns # their tasks IDs. def libs_for(*platforms): - return list(map(lambda p: globals()[p + "_libs"](), platforms)) + is_release = os.environ["TASK_FOR"] == "github-release" + return list(map(lambda p: globals()[p + "_libs"](is_release), platforms)) -def android_libs(): - return ( +def android_libs(is_release): + task = ( linux_build_task("Android libs (all architectures): build") .with_script(""" pushd libs @@ -69,11 +70,14 @@ def android_libs(): .with_artifacts( "/build/repo/target.tar.gz", ) - .find_or_create("build.libs.android." + CONFIG.git_sha_for_directory("libs")) ) + if is_release: + return task.create() + else: + return task.find_or_create("build.libs.android." + CONFIG.git_sha_for_directory("libs")) -def desktop_linux_libs(): - return ( +def desktop_linux_libs(is_release): + task = ( linux_build_task("Desktop libs (Linux): build") .with_script(""" pushd libs @@ -84,11 +88,14 @@ def desktop_linux_libs(): .with_artifacts( "/build/repo/target.tar.gz", ) - .find_or_create("build.libs.desktop.linux." + CONFIG.git_sha_for_directory("libs")) ) + if is_release: + return task.create() + else: + return task.find_or_create("build.libs.desktop.linux." + CONFIG.git_sha_for_directory("libs")) -def desktop_macos_libs(): - return ( +def desktop_macos_libs(is_release): + task = ( linux_cross_compile_build_task("Desktop libs (macOS): build") .with_script(""" pushd libs @@ -99,11 +106,14 @@ def desktop_macos_libs(): .with_artifacts( "/build/repo/target.tar.gz", ) - .find_or_create("build.libs.desktop.macos." + CONFIG.git_sha_for_directory("libs")) ) + if is_release: + return task.create() + else: + return task.find_or_create("build.libs.desktop.macos." + CONFIG.git_sha_for_directory("libs")) -def desktop_win32_x86_64_libs(): - return ( +def desktop_win32_x86_64_libs(is_release): + task = ( linux_build_task("Desktop libs (win32-x86-64): build") .with_script(""" apt-get install --quiet --yes --no-install-recommends mingw-w64 @@ -115,8 +125,11 @@ def desktop_win32_x86_64_libs(): .with_artifacts( "/build/repo/target.tar.gz", ) - .find_or_create("build.libs.desktop.win32-x86-64." + CONFIG.git_sha_for_directory("libs")) ) + if is_release: + return task.create() + else: + return task.find_or_create("build.libs.desktop.win32-x86-64." + CONFIG.git_sha_for_directory("libs")) def android_task(task_name, libs_tasks): task = linux_cross_compile_build_task(task_name) @@ -156,11 +169,7 @@ def gradle_module_task_name(module, gradle_task_name): def gradle_module_task(libs_tasks, module_info, is_release): module = module_info['name'] - if is_release: - task_title = "{} - Build, test and upload to bintray".format(module) - else: - task_title = "{} - Build and test".format(module) - task = android_task(task_title, libs_tasks) + task = android_task("{} - Build and test".format(module), libs_tasks) # This is important as by default the Rust plugin will only cross-compile for Android + host platform. task.with_script('echo "rust.targets=arm,arm64,x86_64,x86,darwin,linux-x86-64,win32-x86-64-gnu\n" > local.properties') if not is_release: # Makes builds way faster. @@ -179,13 +188,9 @@ def gradle_module_task(libs_tasks, module_info, is_release): ) for artifact_info in module_info['artifacts']: task.with_artifacts(artifact_info['artifact']) - if is_release: - if module_info['uploadSymbols']: - task.with_scopes("secrets:get:project/application-services/symbols-token") - task.with_script("./automation/upload_android_symbols.sh {}".format(module_info['path'])) - task.with_scopes("secrets:get:project/application-services/publish") - task.with_script("python automation/taskcluster/release/fetch-bintray-api-key.py") - task.with_script('./gradlew --no-daemon {} --debug -PvcsTag="$GIT_SHA"'.format(gradle_module_task_name(module, "bintrayUpload"))) + if is_release and module_info['uploadSymbols']: + task.with_scopes("secrets:get:project/application-services/symbols-token") + task.with_script("./automation/upload_android_symbols.sh {}".format(module_info['path'])) return task.create() def build_gradle_modules_tasks(is_release): @@ -201,40 +206,54 @@ def android_multiarch(): def android_multiarch_release(): module_build_tasks = build_gradle_modules_tasks(True) - return ( - linux_build_task("All modules - Publish via bintray") - .with_dependencies(*module_build_tasks.values()) - # Our -unpublished- artifacts were uploaded in build_gradle_modules_tasks(), - # however there is not way to just trigger a bintray publish from gradle without - # uploading anything, so we do it manually using curl :( - # We COULD publish each artifact individually, however that would mean if - # a build task fails we end up with a partial release. - # Since we manipulate secrets, we also disable bash debug mode. - .with_script(""" - python automation/taskcluster/release/fetch-bintray-api-key.py - set +x - BINTRAY_USER=$(grep 'bintray.user=' local.properties | cut -d'=' -f2) - BINTRAY_APIKEY=$(grep 'bintray.apikey=' local.properties | cut -d'=' -f2) - PUBLISH_URL=https://api.bintray.com/content/mozilla-appservices/application-services/org.mozilla.appservices/{}/publish - echo "Publishing on $PUBLISH_URL" - curl -X POST -u $BINTRAY_USER:$BINTRAY_APIKEY $PUBLISH_URL - echo "Success!" - set -x - """.format(appservices_version())) - .with_scopes("secrets:get:project/application-services/publish") - .with_features('taskclusterProxy') # So we can fetch the bintray secret. - .create() - ) + + version = appservices_version() + worker_type = os.environ['BEETMOVER_WORKER_TYPE'] + bucket_name = os.environ['BEETMOVER_BUCKET'] + bucket_public_url = os.environ['BEETMOVER_BUCKET_PUBLIC_URL'] + + for module_info in module_definitions(): + module = module_info['name'] + build_task = module_build_tasks[module] + for artifact in module_info['artifacts']: + artifact_name = artifact['name'] + artifact_path = artifact['path'] + ( + BeetmoverTask("Publish Android module: {} via beetmover".format(artifact_name)) + .with_description("Publish release module {} to {}".format(artifact_name, bucket_public_url)) + .with_worker_type(worker_type) + # We want to make sure ALL builds succeeded before doing a release. + .with_dependencies(*module_build_tasks.values()) + .with_upstream_artifact({ + "paths": [artifact_path], + "taskId": build_task, + "taskType": "build", + "zipExtract": True, + }) + .with_app_name("appservices") + .with_artifact_id(artifact_name) + .with_app_version(version) + .with_scopes( + "project:mozilla:application-services:releng:beetmover:bucket:{}".format(bucket_name), + "project:mozilla:application-services:releng:beetmover:action:push-to-maven" + ) + .create() + ) def dockerfile_path(name): return os.path.join(os.path.dirname(__file__), "docker", name + ".dockerfile") - def linux_task(name): - return DockerWorkerTask(name).with_worker_type("application-services-r") - + task = ( + DockerWorkerTask(name) + .with_worker_type("application-services-r") + ) + if os.environ["TASK_FOR"] == "github-release": + task.with_features("chainOfTrust") + return task def linux_build_task(name): + use_indexed_docker_image = os.environ["TASK_FOR"] != "github-release" task = ( linux_task(name) # https://docs.taskcluster.net/docs/reference/workers/docker-worker/docs/caches @@ -251,7 +270,7 @@ def linux_build_task(name): .with_index_and_artifacts_expire_in(build_artifacts_expire_in) .with_artifacts("/build/sccache.log") .with_max_run_time_minutes(120) - .with_dockerfile(dockerfile_path("build")) + .with_dockerfile(dockerfile_path("build"), use_indexed_docker_image) .with_env(**build_env, **linux_build_env) .with_script(""" rustup toolchain install stable diff --git a/automation/taskcluster/decisionlib.py b/automation/taskcluster/decisionlib.py index 38a02ad4ff..f2e0cbcd59 100644 --- a/automation/taskcluster/decisionlib.py +++ b/automation/taskcluster/decisionlib.py @@ -27,7 +27,7 @@ # Public API __all__ = [ "CONFIG", "SHARED", - "Task", "DockerWorkerTask", + "Task", "DockerWorkerTask", "BeetmoverTask", "build_full_task_graph", "populate_chain_of_trust_required_but_unused_files", "populate_chain_of_trust_task_graph", ] @@ -53,9 +53,9 @@ def __init__(self): # Set in the decision task’s payload, such as defined in .taskcluster.yml self.task_owner = os.environ.get("TASK_OWNER") self.task_source = os.environ.get("TASK_SOURCE") - self.git_url = os.environ.get("GIT_URL") - self.git_ref = os.environ.get("GIT_REF") - self.git_sha = os.environ.get("GIT_SHA") + self.git_url = os.environ.get("APPSERVICES_HEAD_REPOSITORY") + self.git_ref = os.environ.get("APPSERVICES_HEAD_BRANCH") + self.git_sha = os.environ.get("APPSERVICES_HEAD_REV") # Map directory string to git sha for that directory. self._git_sha_for_directory = {} @@ -80,6 +80,7 @@ class Shared: """ def __init__(self): self.now = datetime.datetime.utcnow() + self.tasks_cache = {} self.found_or_created_indexed_tasks = {} self.all_tasks = [] @@ -132,7 +133,7 @@ class Task: A task definition, waiting to be created. Typical is to use chain the `with_*` methods to set or extend this object’s attributes, - then call the `crate` or `find_or_create` method to schedule a task. + then call the `create` or `find_or_create` method to schedule a task. This is an abstract class that needs to be specialized for different worker implementations. """ @@ -210,6 +211,7 @@ def create(self): if any(r.startswith("index.") for r in routes): self.extra.setdefault("index", {})["expires"] = \ SHARED.from_now_json(self.index_and_artifacts_expire_in) + dict_update_if_truthy( queue_payload, scopes=scopes, @@ -256,6 +258,53 @@ def find_or_create(self, index_path=None): SHARED.found_or_created_indexed_tasks[index_path] = task_id return task_id + def reuse_or_create(self, cache_id=None): + """ + See if we can re-use a task with the same cache_id or + create a new one, this is similar as `find_or_create` + except that the scope of this function is limited to + its execution since nothing is persisted. + """ + task_id = SHARED.tasks_cache.get(cache_id) + if task_id is not None: + return task_id + task_id = self.create() + SHARED.tasks_cache[cache_id] = task_id + return task_id + +class BeetmoverTask(Task): + def __init__(self, name): + super().__init__(name) + self.provisioner_id = "scriptworker-prov-v1" + self.artifact_id = None + self.app_name = None + self.app_version = None + self.upstream_artifacts = [] + + with_app_name = chaining(setattr, "app_name") + with_app_version = chaining(setattr, "app_version") + with_artifact_id = chaining(setattr, "artifact_id") + with_upstream_artifact = chaining(append_to_attr, "upstream_artifacts") + + def build_worker_payload(self): + payload = { + "maxRunTime": 10 * 60, + "releaseProperties": { + "appName": self.app_name, + }, + "upstreamArtifacts": self.upstream_artifacts, + "version": self.app_version, + "artifact_id": self.artifact_id, + } + + # XXX: Beetmover jobs that transfer the `forUnitTests` maven.zip need + # to have an additional flag set + if 'forUnitTests' in self.name: + payload['is_jar'] = True + + return payload + + class DockerWorkerTask(Task): """ Task definition for a worker type that runs the `generic-worker` implementation. @@ -298,6 +347,10 @@ def build_worker_payload(self): deindent("\n".join(self.scripts)) ], } + if self.features.get("chainOfTrust"): + if isinstance(self.docker_image, dict): + cot = self.extra.setdefault("chainOfTrust", {}) + cot.setdefault('inputs', {})['docker-image'] = self.docker_image['taskId'] return dict_update_if_truthy( worker_payload, env=self.env, @@ -351,11 +404,11 @@ def with_repo(self): .with_env(**git_env()) \ .with_early_script(""" cd repo - git fetch --quiet --tags "$GIT_URL" "$GIT_REF" - git reset --hard "$GIT_SHA" + git fetch --quiet --tags "$APPSERVICES_HEAD_REPOSITORY" "$APPSERVICES_HEAD_BRANCH" + git reset --hard "$APPSERVICES_HEAD_REV" """) - def with_dockerfile(self, dockerfile): + def with_dockerfile(self, dockerfile, use_indexed_task=True): """ Build a Docker image based on the given `Dockerfile`, and use it for this task. @@ -393,15 +446,21 @@ def with_dockerfile(self, dockerfile): "servobrowser/taskcluster-bootstrap:image-builder@sha256:" \ "0a7d012ce444d62ffb9e7f06f0c52fedc24b68c2060711b313263367f7272d9d" ) - .find_or_create("docker-image." + digest) ) + if self.features.get("chainOfTrust"): + image_build_task.with_features("chainOfTrust") + task_index = "appservices-docker-image." + digest + if use_indexed_task: + image_build_task_id = image_build_task.find_or_create(task_index) + else: + image_build_task_id = image_build_task.reuse_or_create(task_index) return self \ - .with_dependencies(image_build_task) \ + .with_dependencies(image_build_task_id) \ .with_docker_image({ "type": "task-image", "path": "public/image.tar.lz4", - "taskId": image_build_task, + "taskId": image_build_task_id, }) @@ -428,9 +487,9 @@ def git_env(): assert CONFIG.git_ref assert CONFIG.git_sha return { - "GIT_URL": CONFIG.git_url, - "GIT_REF": CONFIG.git_ref, - "GIT_SHA": CONFIG.git_sha, + "APPSERVICES_HEAD_REPOSITORY": CONFIG.git_url, + "APPSERVICES_HEAD_BRANCH": CONFIG.git_ref, + "APPSERVICES_HEAD_REV": CONFIG.git_sha, } def dict_update_if_truthy(d, **kwargs): diff --git a/automation/taskcluster/mock.py b/automation/taskcluster/mock.py index e9a4844763..262c323c93 100755 --- a/automation/taskcluster/mock.py +++ b/automation/taskcluster/mock.py @@ -41,7 +41,7 @@ def findTask(self, _): sys.modules["taskcluster"] = sys.modules[__name__] sys.dont_write_bytecode = True os.environ.update(**{k: k for k in "TASK_ID TASK_OWNER TASK_SOURCE GIT_URL GIT_SHA".split()}) -os.environ["GIT_REF"] = "refs/heads/auto" +os.environ["APPSERVICES_HEAD_BRANCH"] = "refs/heads/auto" import decision_task as decision_task print("\n# Push:") diff --git a/automation/taskcluster/release/fetch-bintray-api-key.py b/automation/taskcluster/release/fetch-bintray-api-key.py deleted file mode 100644 index ffe49322cf..0000000000 --- a/automation/taskcluster/release/fetch-bintray-api-key.py +++ /dev/null @@ -1,32 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import taskcluster - -SECRET_NAME = 'project/application-services/publish' -TASKCLUSTER_BASE_URL = 'http://taskcluster/secrets/v1' - - -def fetch_publish_secrets(secret_name): - """Fetch and return secrets from taskcluster's secret service""" - secrets = taskcluster.Secrets({'baseUrl': TASKCLUSTER_BASE_URL}) - return secrets.get(secret_name) - - -def main(): - """Fetch the bintray user and api key from taskcluster's secret service - and save it to local.properties in the project root directory. - """ - print('fetching {} ...'.format(SECRET_NAME)) - data = fetch_publish_secrets(SECRET_NAME) - print('fetching {} ... DONE ({} bytes)'.format(SECRET_NAME, len(str(data)))) - - properties_file_path = os.path.join(os.path.dirname(__file__), '../../../local.properties') - with open(properties_file_path, 'w') as properties_file: - properties_file.write("bintray.user=%s\n" % data['secret']['bintray_user']) - properties_file.write("bintray.apikey=%s\n" % data['secret']['bintray_apikey']) - -if __name__ == "__main__": - main() diff --git a/build.gradle b/build.gradle index 46a7ba42f7..f62f6faac0 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,6 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // Publish. - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' classpath 'digital.wup:android-maven-publish:3.6.2' classpath 'gradle.plugin.org.mozilla.rust-android-gradle:plugin:0.8.1' @@ -75,8 +74,11 @@ import com.sun.jna.Platform as DefaultPlatform ext.libsGitSha = "git --git-dir=${rootProject.rootDir}/.git diff --name-only master -- :/libs".execute().text.allWhitespace ? "git --git-dir=${rootProject.rootDir}/.git rev-parse HEAD:libs".execute().text.trim() : null +// Use in-tree libs from the source directory in CI or if the git SHA is unset; otherwise use +// downloaded libs. +def useDownloadedLibs = !System.getenv('CI') && ext.libsGitSha != null -if (rootProject.ext.libsGitSha != null) { +if (useDownloadedLibs) { task downloadAndroidLibs(type: Download) { src "https://index.taskcluster.net/v1/task/project.application-services.application-services.build.libs.android.${rootProject.ext.libsGitSha}/artifacts/public/target.tar.gz" dest new File(buildDir, "libs.android.${rootProject.ext.libsGitSha}.tar.gz") @@ -205,9 +207,7 @@ switch (DefaultPlatform.RESOURCE_PREFIX) { break } -// Use in-tree libs from the source directory in CI or if the git SHA is unset; otherwise use -// downloaded libs. -def libsRootDir = (System.getenv('CI') || ext.libsGitSha == null) ? rootProject.rootDir : rootProject.buildDir +def libsRootDir = useDownloadedLibs ? rootProject.buildDir : rootProject.rootDir subprojects { apply plugin: 'digital.wup.android-maven-publish' diff --git a/gradle-plugin/README.md b/gradle-plugin/README.md index c5715c5a62..dc9e8882c7 100644 --- a/gradle-plugin/README.md +++ b/gradle-plugin/README.md @@ -176,25 +176,6 @@ configuration.resolutionStrategy.dependencySubstitution.all { dependency -> } ``` -## Application Services Maven repository details - -The megazord libraries and dependencies aren't yet published to maven.mozilla.org (see -[issue #252](https://github.com/mozilla/application-services/issues/252)) and for technical reasons -they aren't yet mirrored to jcenter either (see -[this bintray plugin issue](https://github.com/bintray/gradle-bintray-plugin/issues/130)). - -That means we need a [non-standard Maven repository](https://bintray.com/mozilla-appservices/application-services): -```groovy -repositories { - maven { - name 'appservices' - url 'https://dl.bintray.com/mozilla-appservices/application-services' - } -} -``` - -The Gradle plugin adds such needed repositories automatically. - ## Unit testing Rust native code The Application Services Maven publications contain Rust native code targeting Android devices. To diff --git a/libs/README.md b/libs/README.md index 68b882ce39..47b84d4866 100644 --- a/libs/README.md +++ b/libs/README.md @@ -9,7 +9,7 @@ This directory builds `openssl` for iOS, Android and desktop platforms. * `./build-all.sh desktop` - Build for Desktop -### Supported Arch +### Supported architectures * Android: `TARGET_ARCHS=("x86" "x86_64" "arm64" "arm")` * iOS: `TARGET_ARCHS=("x86_64" "arm64")` diff --git a/publish.gradle b/publish.gradle index 4cd6601350..4c3b3861c5 100644 --- a/publish.gradle +++ b/publish.gradle @@ -208,51 +208,6 @@ ext.configurePublish = { jnaForTestConfiguration = null, } } - apply plugin: 'com.jfrog.bintray' - - // It feels like this shouldn't be necessary, but without it an - // "unspecified" creeps into bintray URLs -- just like - // https://github.com/bintray/gradle-bintray-plugin/issues/244, but not - // fixed by gradle-bintray-plugin:1.8.4. - version = rootProject.ext.library.version - - Properties localProperties = null; - if (project.rootProject.file('local.properties').canRead()) { - localProperties = new Properties() - localProperties.load(project.rootProject.file('local.properties').newDataInputStream()) - } - - def thePublications = ['aar'] - if (variantWithoutLib != null) { - thePublications << 'aarWithoutLib' - } - if (jnaForTestConfiguration != null) { - thePublications << 'forUnitTestsJar' - } - - bintray { - user = localProperties != null ? localProperties.getProperty("bintray.user") : "" - key = localProperties != null ? localProperties.getProperty("bintray.apikey") : "" - - publications = thePublications - - pkg { - repo = libRepositoryName - name = libProjectName - // Packages are uploaded with bintray username ncalexander but end - // up at https://bintray.com/mozilla-appservices. - userOrg = 'mozilla-appservices' - websiteUrl = libUrl - vcsUrl = libVcsUrl - if (project.ext.has('vcsTag')) { - vcsTag = project.ext.vcsTag - } - licenses = [libLicense] - publish = false - publicDownloadNumbers = true - } - } - task zipMavenArtifacts publishing.publications.withType(MavenPublication).each {publication ->