This is an automated email from the ASF dual-hosted git repository.

cpcloud pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/master by this push:
     new 58a2366  ARROW-2676: [Packaging] Deploy build artifacts to github 
releases
58a2366 is described below

commit 58a23668fe2cb518f7ecb41bd16886dba82ff5d9
Author: Krisztián Szűcs <[email protected]>
AuthorDate: Sun Jun 24 14:32:47 2018 -0400

    ARROW-2676: [Packaging] Deploy build artifacts to github releases
    
    ~Added a new task which trigger crossbow builds on master@crossbow.~
    ~See travis output https://travis-ci.org/kszucs/crossbow/builds/388667590~
    
    Here are the boxes we need to check:
    
     - [x] Create a separate tagged branch that contains a YAML file indicating 
the information about each task created as part of the run. So there should be 
one entry for each job that was created -- the git hash for the task, the CI 
service used to run the task, etc. It should also indicate if one or more 
artifacts are expected to be uploaded
     - [x] Write a status tool which can query the status of a particular run 
and determine if the run is complete (needs cleanup)
    
     - [x] Can we run each desired task in a particular CI service
     - [x] We can determine the list of created tasks associated with a 
particular run
     - [x] Tasks should be configured with the tag name, and artifacts should 
be uploaded to GitHub under the tag which should appear as a release on the repo
     - [x] Each task can upload its artifacts to a deterministic central 
location (e.g. GitHub), where the artifacts are not commingled with any other 
run
    -> only linux packages are failing, I suggest resolving it in a subsequent 
PR (issue https://issues.apache.org/jira/browse/ARROW-2713)
     - [x] ~~We can determine whether all the expected artifacts from a 
particular run have been successfully uploaded (i.e. to GitHub)~~ to be done in 
https://issues.apache.org/jira/browse/ARROW-2724
     - [x] We can download all the artifacts from a successful run and GPG sign 
them for purposes of a release vote
    
    Example of artifacts available here 
https://github.com/kszucs/crossbow/releases
    Jobs and tasks here https://github.com/kszucs/crossbow/branches
    Job definition here https://github.com/kszucs/crossbow/blob/build-36/job.yml
    
    Author: Krisztián Szűcs <[email protected]>
    Author: Phillip Cloud <[email protected]>
    
    Closes #2109 from kszucs/nightly and squashes the following commits:
    
    87860d00 <Krisztián Szűcs> batch correctly rename double extensions
    83f333b5 <Krisztián Szűcs> don't use dirty flag in version number
    0701d640 <Krisztián Szűcs> fixed conda-win deployments
    062da7c7 <Krisztián Szűcs> conda win renames
    b892471c <Krisztián Szűcs> explicit task names instead of placeholder in 
readme; foxme note for linux-packages
    e4e971c2 <Krisztián Szűcs> don't depend on any previous commit
    2e9fcf9a <Krisztián Szűcs> remove logging config
    f317a06b <Krisztián Szűcs> outdated readme section
    337234d2 <Phillip Cloud> Validate submit tasks
    12efaba6 <Phillip Cloud> Validate github token
    1b3d5fb5 <Phillip Cloud> Code formatting cleanups
    4c64db6f <Phillip Cloud> Add email property and use target repo user.email 
by default
    9b222380 <Krisztián Szűcs> update tasks
    0561e622 <Krisztián Szűcs> force update conda win assets
    71b69436 <Krisztián Szűcs> postfix conda pkgs with arch
    bfb5eaec <Krisztián Szűcs> fix osx wheel builkds
    95bc3faf <Krisztián Szűcs> retrieve commit's combined status
    0773f3ab <Krisztián Szűcs> call rake version update
    af6dd480 <Krisztián Szűcs> remove flag
    167a3938 <Krisztián Szűcs> gry to remove osx wheel flag
    ccbc1508 <Krisztián Szűcs> little more status context
    14da2d5d <Krisztián Szűcs> build prefix
    fd6bd231 <Krisztián Szűcs> print build id
    5c6e1541 <Krisztián Szűcs> fix conda-win deployments
    8c069c6b <Krisztián Szűcs> set autoincremented job_id outside of queue put
    53748668 <Krisztián Szűcs> cleanup status check and artifact download
    0b79d13d <Krisztián Szűcs> trying to remove python dependency from 
parquet-cpp's recipe
    96e101af <Krisztián Szűcs> draft for downloading artifacts
    55e7ccb2 <Krisztián Szűcs> query statuses API
    edeee717 <Krisztián Szűcs> refactooooor
    bd5ece4a <Krisztián Szűcs> rename trigger build; add license header
    a0a81275 <Krisztián Szűcs> tigger builds template
    8a5fcb01 <Krisztián Szűcs> trigger build task; explicit task names during 
submission
    b65cb3f2 <Krisztián Szűcs> track remote branches
    0ab0567f <Krisztián Szűcs> don't create tree based on master
---
 cpp/CMakeLists.txt                                 |   3 +
 dev/tasks/README.md                                |  81 ++--
 dev/tasks/conda-recipes/appveyor.yml               |  26 +-
 dev/tasks/conda-recipes/parquet-cpp/meta.yaml      |   7 +-
 dev/tasks/conda-recipes/travis.linux.yml           |  18 +-
 dev/tasks/conda-recipes/travis.osx.yml             |  18 +-
 .../travis.linux.yml                               |  59 +--
 dev/tasks/crossbow.py                              | 487 +++++++++++++--------
 dev/tasks/linux-packages/travis.linux.yml          |  16 +-
 dev/tasks/python-wheels/appveyor.yml               |   9 +-
 dev/tasks/python-wheels/travis.linux.yml           |  10 +
 dev/tasks/python-wheels/travis.osx.yml             |  21 +-
 dev/tasks/tasks.yml                                |  31 +-
 13 files changed, 478 insertions(+), 308 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 5f29bda..9ac59e9 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -343,6 +343,9 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ARROW_CXXFLAGS}")
 # For any C code, use the same flags.
 set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS}")
 
+# Remove --std=c++11 to avoid errors from C compilers
+string(REPLACE "-std=c++11" "" CMAKE_C_FLAGS ${CMAKE_C_FLAGS})
+
 # Add C++-only flags, like -std=c++11
 set(CMAKE_CXX_FLAGS "${CXX_ONLY_FLAGS} ${CMAKE_CXX_FLAGS}")
 
diff --git a/dev/tasks/README.md b/dev/tasks/README.md
index f1f0128..35140fa 100644
--- a/dev/tasks/README.md
+++ b/dev/tasks/README.md
@@ -80,12 +80,12 @@ submission. The tasks are defined in `tasks.yml`
 6. Install the python dependencies for the script:
 
    ```bash
-   conda install -y jinja2 pygit2 click pyyaml
+   conda install -y jinja2 pygit2 click pyyaml setuptools_scm github3.py
    ```
 
    ```bash
    # pygit2 requires libgit2: http://www.pygit2.org/install.html
-   pip install -y jinja2 pygit2 click pyyaml
+   pip install -y jinja2 pygit2 click pyyaml setuptools_scm github3.py
    ```
 
 7. Try running it:
@@ -106,7 +106,7 @@ The script does the following:
    $ git clone https://github.com/kszucs/crossbow
 
    $ cd arrow/dev/tasks
-   $ python crossbow.py
+   $ python crossbow.py submit conda-win conda-linux conda-osx
    ```
 
 2. Gets the HEAD commit of the currently checked out branch and generates
@@ -115,7 +115,7 @@ The script does the following:
 
    ```bash
    git checkout ARROW-<ticket number>
-   python dev/tasks/crossbow.py --dry-run
+   python dev/tasks/crossbow.py submit --dry-run conda-linux conda-osx
    ```
 
    > Note that the arrow branch must be pushed beforehand, because the script
@@ -123,86 +123,57 @@ The script does the following:
 
 3. Reads and renders the required build configurations with the parameters
    substituted.
-2. Create a commit per build configuration to its own branch. For example
-   to build `travis-linux-conda.yml` it will place a commit to the tip of
-   `crossbow@travis-linux-conda` branch.
+2. Create a branch per task, prefixed with the job id. For example
+   to build conda recipes on linux it will create a new branch:
+   `crossbow@build-<id>-conda-linux`.
 3. Pushes the modified branches to GitHub which triggers the builds.
    For authentication it uses github oauth tokens described in the install
    section.
 
 
-### Examples
-
-The script accepts a pattern as a first argument to narrow the build scope:
-
-Run all builds:
+### Query the build status
 
 ```bash
-$ python crossbow.py
-Repository: https://github.com/kszucs/arrow@tasks
-Commit SHA: 810a718836bb3a8cefc053055600bdcc440e6702
-Version: 0.9.1.dev48+g810a7188.d20180414
-Pushed branches:
- - travis-osx-wheel
- - travis-linux-packages
- - travis-linux-wheel
- - appveyor-win-wheel
- - appveyor-win-conda
- - travis-linux-conda
- - travis-osx-conda
+python crossbow.py status <build id / branch name>
 ```
 
-Just render without applying or committing the changes:
+### Download the build artifacts
 
 ```bash
-$ python crossbow.py --dry-run
+python crossbow.py artifacts <build id / branch name>
 ```
 
-Run only `conda` package builds but on all platforms:
+### Examples
+
+The script accepts a pattern as a first argument to narrow the build scope:
+
+Run multiple builds:
 
 ```bash
-$ python crossbow.py conda
+$ python crossbow.py submit linux-packages conda-linux wheel-win
 Repository: https://github.com/kszucs/arrow@tasks
 Commit SHA: 810a718836bb3a8cefc053055600bdcc440e6702
 Version: 0.9.1.dev48+g810a7188.d20180414
 Pushed branches:
- - appveyor-win-conda
- - travis-linux-conda
- - travis-osx-conda
+ - linux-packages
+ - conda-linux
+ - wheel-win
 ```
 
-Run `wheel` builds:
+Just render without applying or committing the changes:
 
 ```bash
-$ python crossbow.py wheel
-Repository: https://github.com/kszucs/arrow@tasks
-Commit SHA: 810a718836bb3a8cefc053055600bdcc440e6702
-Version: 0.9.1.dev48+g810a7188.d20180414
-Pushed branches:
- - travis-osx-wheel
- - travis-linux-wheel
- - appveyor-win-wheel
+$ python crossbow.py submit --dry-run task_name
 ```
 
-Run `osx` builds:
+Run only `conda` package builds but on all platforms:
 
 ```bash
-$ python crossbow.py osx
-Repository: https://github.com/kszucs/arrow@tasks
-Commit SHA: cad1df2c7f650ad3434319bbbefed0d4abe45e4a
-Version: 0.9.1.dev130+gcad1df2c.d20180414
-Pushed branches:
- - travis-osx-wheel
- - travis-osx-conda
+$ python crossbow.py submit conda-win conda-osx conda-linux
 ```
 
-Run only `linux-conda` package build:
+Run `wheel` builds:
 
 ```bash
-$ python crossbow.py linux-conda
-Repository: https://github.com/kszucs/arrow@tasks
-Commit SHA: 810a718836bb3a8cefc053055600bdcc440e6702
-Version: 0.9.1.dev48+g810a7188.d20180414
-Pushed branches:
- - travis-linux-conda
+$ python crossbow.py submit wheel-osx wheel-linux wheel-win
 ```
diff --git a/dev/tasks/conda-recipes/appveyor.yml 
b/dev/tasks/conda-recipes/appveyor.yml
index 10ef704..3d3f330 100644
--- a/dev/tasks/conda-recipes/appveyor.yml
+++ b/dev/tasks/conda-recipes/appveyor.yml
@@ -23,7 +23,6 @@ environment:
     - TARGET_ARCH: x64
       CONDA_PY: 36
       CONDA_INSTALL_LOCN: C:\\Miniconda36-x64
-  ARROW_SRC: C:\apache-arrow
   ARROW_VERSION: {{ ARROW_VERSION }}
 
 install:
@@ -45,11 +44,28 @@ install:
 build: off
 
 test_script:
-  - git clone -b {{ ARROW_BRANCH }} {{ ARROW_REPO }} %ARROW_SRC% || exit /B
-  - git checkout {{ ARROW_SHA }} || exit /B
-  - pushd %ARROW_SRC%\dev\tasks\conda-recipes
-  - conda.exe build parquet-cpp arrow-cpp pyarrow
+  - git clone -b {{ ARROW_BRANCH }} {{ ARROW_REPO }} arrow || exit /B
+  - git -C arrow checkout {{ ARROW_SHA }} || exit /B
+  - pushd arrow\dev\tasks\conda-recipes
+  - conda.exe build --output-folder . parquet-cpp arrow-cpp pyarrow
+  - pushd win-64
+  - for %%f in (*.tar.bz2) do (
+      set %%g=%%~nf
+      ren "%%f" "%%~ng-win-64.tar.bz2"
+    )
 
+artifacts:
+  # this must be relative and child of the build C:\projects\crossbow directory
+  - path: arrow\dev\tasks\conda-recipes\win-64\*.tar.bz2
+
+deploy:
+  release: {{ BUILD_TAG }}
+  provider: GitHub
+  auth_token: "%CROSSBOW_GITHUB_TOKEN%"
+  artifact: /.*\.tar\.bz2/
+  draft: false
+  prerelease: false
+  force_update: true
 
 notifications:
   - provider: Email
diff --git a/dev/tasks/conda-recipes/parquet-cpp/meta.yaml 
b/dev/tasks/conda-recipes/parquet-cpp/meta.yaml
index 0f5a619..e7be2a1 100644
--- a/dev/tasks/conda-recipes/parquet-cpp/meta.yaml
+++ b/dev/tasks/conda-recipes/parquet-cpp/meta.yaml
@@ -36,9 +36,8 @@ source:
 
 build:
   number: 0
-  skip: true  # [win and not (py35 and win64)]
   features:
-    - vc14  # [win and py35]
+    - vc14  # [win]
 
 requirements:
   build:
@@ -46,16 +45,12 @@ requirements:
     - boost-cpp 1.66.0
     - cmake
     - thrift-cpp >=0.11
-    - python            # [win]
     - arrow-cpp {{ ARROW_VERSION }}
 
   run:
     - arrow-cpp {{ ARROW_VERSION }}
 
 test:
-  requires:
-    - python {{ environ['PY_VER'] + '*' }}  # [win]
-
   commands:
     - test -f $PREFIX/lib/libparquet.so                                       
# [linux]
     - test -f $PREFIX/lib/libparquet.dylib                                    
# [osx]
diff --git a/dev/tasks/conda-recipes/travis.linux.yml 
b/dev/tasks/conda-recipes/travis.linux.yml
index c526681..84ca83b 100644
--- a/dev/tasks/conda-recipes/travis.linux.yml
+++ b/dev/tasks/conda-recipes/travis.linux.yml
@@ -26,6 +26,7 @@ env:
     - CONDA_PY=35
     - CONDA_PY=36
   global:
+    - TRAVIS_TAG={{ BUILD_TAG }}
     - ARROW_VERSION={{ ARROW_VERSION }}
 
 install:
@@ -55,7 +56,22 @@ script:
   - git clone -b {{ ARROW_BRANCH }} {{ ARROW_REPO }} arrow
   - git -C arrow checkout {{ ARROW_SHA }}
   - pushd arrow/dev/tasks/conda-recipes
-  - conda build parquet-cpp arrow-cpp pyarrow
+  - conda build --output-folder . parquet-cpp arrow-cpp pyarrow
+  - |
+    pushd linux-64
+    for file in *.tar.bz2; do
+      mv "$file" "$(basename "$file" .tar.bz2)-linux-64.tar.bz2"
+    done
+    popd
+
+deploy:
+  provider: releases
+  api_key: $CROSSBOW_GITHUB_TOKEN
+  file_glob: true
+  file: $TRAVIS_BUILD_DIR/arrow/dev/tasks/conda-recipes/linux-64/*.tar.bz2
+  skip_cleanup: true
+  on:
+    tags: true
 
 notifications:
   email:
diff --git a/dev/tasks/conda-recipes/travis.osx.yml 
b/dev/tasks/conda-recipes/travis.osx.yml
index 8a38d33..31c1c24 100644
--- a/dev/tasks/conda-recipes/travis.osx.yml
+++ b/dev/tasks/conda-recipes/travis.osx.yml
@@ -26,6 +26,7 @@ env:
     - CONDA_PY=35
     - CONDA_PY=36
   global:
+    - TRAVIS_TAG={{ BUILD_TAG }}
     - ARROW_VERSION={{ ARROW_VERSION }}
 
 before_install:
@@ -64,7 +65,22 @@ script:
   - git clone -b {{ ARROW_BRANCH }} {{ ARROW_REPO }} arrow
   - git -C arrow checkout {{ ARROW_SHA }}
   - pushd arrow/dev/tasks/conda-recipes
-  - conda build parquet-cpp arrow-cpp pyarrow
+  - conda build --output-folder . parquet-cpp arrow-cpp pyarrow
+  - |
+    pushd osx-64
+    for file in *.tar.bz2; do
+      mv "$file" "$(basename "$file" .tar.bz2)-osx-64.tar.bz2"
+    done
+    popd
+
+deploy:
+  provider: releases
+  api_key: $CROSSBOW_GITHUB_TOKEN
+  file_glob: true
+  file: $TRAVIS_BUILD_DIR/arrow/dev/tasks/conda-recipes/osx-64/*.tar.bz2
+  skip_cleanup: true
+  on:
+    tags: true
 
 notifications:
   email:
diff --git a/dev/tasks/conda-recipes/travis.linux.yml 
b/dev/tasks/config-nightlies/travis.linux.yml
similarity index 53%
copy from dev/tasks/conda-recipes/travis.linux.yml
copy to dev/tasks/config-nightlies/travis.linux.yml
index c526681..bf2774b 100644
--- a/dev/tasks/conda-recipes/travis.linux.yml
+++ b/dev/tasks/config-nightlies/travis.linux.yml
@@ -6,29 +6,24 @@
 # "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
+# 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.
+# 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.
 
-language: generic
+branches:
+  # don't attempt to build branches intented for windows builds
+  except:
+    - /.*win.*/
 
 os: linux
 dist: trusty
+language: generic
 
-env:
-  matrix:
-    - CONDA_PY=27
-    - CONDA_PY=35
-    - CONDA_PY=36
-  global:
-    - ARROW_VERSION={{ ARROW_VERSION }}
-
-install:
+before_install:
     # Install Miniconda.
     - echo `pwd`
     - |
@@ -48,15 +43,25 @@ install:
       conda config --add channels defaults
       conda config --add channels conda-forge
       conda config --set show_channel_urls true
-      conda install --yes --quiet conda-forge-build-setup
-      source run_conda_forge_build_setup
+
+install:
+  - conda install -y -q jinja2 pygit2 click pyyaml setuptools_scm
 
 script:
-  - git clone -b {{ ARROW_BRANCH }} {{ ARROW_REPO }} arrow
-  - git -C arrow checkout {{ ARROW_SHA }}
-  - pushd arrow/dev/tasks/conda-recipes
-  - conda build parquet-cpp arrow-cpp pyarrow
-
-notifications:
-  email:
-    - {{ EMAIL }}
+  # fetch all branches of crossbow
+  - git config remote.origin.fetch +refs/heads/*:refs/remotes/origin/*
+
+  # clone arrow with crossbow tool
+  - pushd ..
+  - git clone -b {{ ARROW_BRANCH }} {{ ARROW_REPO }}
+
+  # submit packaging tasks
+  - |
+    python arrow/dev/tasks/crossbow.py \
+      conda-linux \
+      conda-win \
+      conda-osx \
+      wheel-linux \
+      wheel-win \
+      wheel-osx \
+      linux-packages
diff --git a/dev/tasks/crossbow.py b/dev/tasks/crossbow.py
index fd4f732..a2a29eb 100755
--- a/dev/tasks/crossbow.py
+++ b/dev/tasks/crossbow.py
@@ -17,12 +17,13 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import os
 import re
-import sys
 import yaml
 import time
 import click
 import pygit2
+import github3
 import logging
 
 from enum import Enum
@@ -30,15 +31,6 @@ from pathlib import Path
 from textwrap import dedent
 from jinja2 import Template
 from setuptools_scm import get_version
-from setuptools_scm.version import simplified_semver_version, meta
-
-
-logging.basicConfig(
-    level=logging.INFO,
-    format="[%(asctime)s] %(levelname)s Crossbow %(message)s",
-    datefmt="%H:%M:%S",
-    stream=click.get_text_stream('stdout')
-)
 
 
 class GitRemoteCallbacks(pygit2.RemoteCallbacks):
@@ -70,134 +62,81 @@ class GitRemoteCallbacks(pygit2.RemoteCallbacks):
             return None
 
 
-class Platform(Enum):
-    # in alphabetical order
-    LINUX = 0
-    OSX = 1
-    WIN = 2
-
-    @property
-    def ci(self):
-        if self is self.WIN:
-            return 'appveyor'
-        else:
-            return 'travis'
-
-    @property
-    def filename(self):
-        if self.ci == 'appveyor':
-            return 'appveyor.yml'
-        else:
-            return '.travis.yml'
-
+class Repo(object):
 
-class Target(object):
-
-    def __init__(self, repo_path, template_directory=None):
+    def __init__(self, repo_path):
         self.path = Path(repo_path).absolute()
-
-        # relative to repository's path
-        if template_directory is None:
-            self.templates = self.path
-        else:
-            self.templates = self.path / template_directory
-
-        # initialize a repo object to interact with arrow's git data
         self.repo = pygit2.Repository(str(self.path))
 
-        msg = dedent('''
-            Repository: {remote}@{branch}
-            Commit SHA: {sha}
-            Version: {version}
+    def __str__(self):
+        tpl = dedent('''
+            Repo: {remote}@{branch}
+            Commit: {head}
         ''')
-        logging.info(msg.format(
-            remote=self.current_remote.url,
-            branch=self.current_branch.branch_name,
-            sha=self.sha,
-            version=self.version
-        ))
+        return tpl.format(
+            remote=self.remote.url,
+            branch=self.branch.branch_name,
+            head=self.head
+        )
+
+    def fetch(self):
+        self.origin.fetch()
 
     @property
-    def sha(self):
+    def head(self):
         """Currently checked out commit's sha"""
         return self.repo.head.target
 
     @property
-    def version(self):
-        """Generate version number based on version control history"""
-        # TODO(kszucs) use self.repo.describe() instead
-        return get_version(self.path)
+    def branch(self):
+        """Currently checked out branch"""
+        reference = self.repo.head.shorthand
+        return self.repo.branches[reference]
 
     @property
-    def current_remote(self):
-        remote_name = self.current_branch.upstream.remote_name
+    def remote(self):
+        """Currently checked out branch's remote counterpart"""
+        remote_name = self.branch.upstream.remote_name
         return self.repo.remotes[remote_name]
 
     @property
-    def current_branch(self):
-        reference = self.repo.head.shorthand
-        return self.repo.branches[reference]
+    def origin(self):
+        return self.repo.remotes['origin']
 
     @property
-    def description(self):
-        return '[BUILD] {} of {}@{}'.format(self.version,
-                                            self.current_remote.url,
-                                            self.current_branch.branch_name)
-
-
-class Build(object):
-
-    def __init__(self, target, name, platform, template, **params):
-        assert isinstance(target, Target)
-        assert isinstance(platform, Platform)
-
-        self.name = name
-        self.target = target
-        self.platform = platform
-        self.template = template
-        self.params = params
-
-    def render(self):
-        path = Path(self.template)
-        template = Template(path.read_text())
-        return template.render(**self.params)
-
-    def config_files(self):
-        return {self.platform.filename: self.render()}
+    def email(self):
+        return next(self.repo.config.get_multivar('user.email'))
 
     @property
-    def branch(self):
-        return self.name
+    def signature(self):
+        name = next(self.repo.config.get_multivar('user.name'))
+        return pygit2.Signature(name, self.email, int(time.time()))
 
-    @property
-    def description(self):
-        return self.target.description
+    def parse_user_repo(self):
+        m = re.match('.*\/([^\/]+)\/([^\/\.]+)(\.git)?$', self.remote.url)
+        user, repo = m.group(1), m.group(2)
+        return user, repo
 
 
-class Queue(object):
+class Queue(Repo):
 
     def __init__(self, repo_path):
-        self.path = Path(repo_path).absolute()
-        self.repo = pygit2.Repository(str(self.path))
-        self.updated_branches = []
-
-    def _get_parent_commit(self):
-        """Currently this always returns the HEAD of master"""
-        master = self.repo.branches['master']
-        return self.repo[master.target]
-
-    def _get_or_create_branch(self, name):
-        try:
-            return self.repo.branches[name]
-        except KeyError:
-            parent = self._get_parent_commit()
-            return self.repo.branches.create(name, parent)
-
-    def _create_tree(self, files):
-        parent = self._get_parent_commit()
+        super(Queue, self).__init__(repo_path)
+        self._updated_refs = []
+
+    def next_job_id(self, prefix):
+        """Auto increments the branch's identifier based on the prefix"""
+        pattern = re.compile(prefix + '-(\d+)')
+        matches = list(filter(None, map(pattern.match, self.repo.branches)))
+        if matches:
+            latest = max(int(m.group(1)) for m in matches)
+        else:
+            latest = 0
+        return '{}-{}'.format(prefix, latest + 1)
 
-        # creating the tree we are going to push based on master's tree
-        builder = self.repo.TreeBuilder(parent.tree)
+    def _create_branch(self, branch_name, files, parents=[], message=''):
+        # 1. create tree
+        builder = self.repo.TreeBuilder()
 
         for filename, content in files.items():
             # insert the file and creating the new filetree
@@ -205,119 +144,293 @@ class Queue(object):
             builder.insert(filename, blob_id, pygit2.GIT_FILEMODE_BLOB)
 
         tree_id = builder.write()
-        return tree_id
 
-    def put(self, build):
-        assert isinstance(build, Build)
+        # 2. create commit with the tree created above
+        author = committer = self.signature
+        commit_id = self.repo.create_commit(None, author, committer, message,
+                                            tree_id, parents)
+        commit = self.repo[commit_id]
 
-        branch = self._get_or_create_branch(build.branch)
-        tree_id = self._create_tree(build.config_files())
+        # 3. create branch pointing to the previously created commit
+        branch = self.repo.create_branch(branch_name, commit)
+        # append to the pushable references
+        self._updated_refs.append('refs/heads/{}'.format(branch_name))
 
-        # creating the new commit
-        timestamp = int(time.time())
+        return branch
 
-        name = next(self.repo.config.get_multivar('user.name'))
-        email = next(self.repo.config.get_multivar('user.email'))
+    def _create_tag(self, tag_name, commit_id, message=''):
+        tag_id = self.repo.create_tag(tag_name, commit_id,
+                                      pygit2.GIT_OBJ_COMMIT, self.signature,
+                                      message)
+
+        # append to the pushable references
+        self._updated_refs.append('refs/tags/{}'.format(tag_name))
+
+        return self.repo[tag_id]
+
+    def put(self, job, prefix='build'):
+        assert isinstance(job, Job)
+        assert job.branch is not None
 
-        author = pygit2.Signature('crossbow', '[email protected]',
-                                  int(timestamp))
-        committer = pygit2.Signature(name, email, int(timestamp))
-        message = build.description
+        # create tasks' branches
+        for task_name, task in job.tasks.items():
+            branch = self._create_branch(task.branch, files=task.files())
+            task.commit = str(branch.target)
 
-        reference = 'refs/heads/{}'.format(branch.branch_name)
-        commit_id = self.repo.create_commit(reference, author, committer,
-                                            message, tree_id, [branch.target])
-        logging.info('{} created on {}'.format(
-            commit_id, branch.branch_name))
+        # create job's branch
+        branch = self._create_branch(job.branch, files=job.files())
+        self._create_tag(job.branch, branch.target)
 
-        self.updated_branches.append(branch)
+        return branch
 
     def push(self, token):
         callbacks = GitRemoteCallbacks(token)
+        self.origin.push(self._updated_refs, callbacks=callbacks)
+        self.updated_refs = []
 
-        remote = self.repo.remotes['origin']
-        refs = [branch.name for branch in self.updated_branches]
-        shorthands = [b.shorthand for b in self.updated_branches]
 
-        remote.push(refs, callbacks=callbacks)
-        self.updated_branches = []
+class Platform(Enum):
+    # in alphabetical order
+    LINUX = 0
+    OSX = 1
+    WIN = 2
+
+    @property
+    def ci(self):
+        if self is self.WIN:
+            return 'appveyor'
+        else:
+            return 'travis'
+
+    @property
+    def filename(self):
+        if self.ci == 'appveyor':
+            return 'appveyor.yml'
+        else:
+            return '.travis.yml'
+
+
+class Task(object):
+
+    def __init__(self, platform, template, commit=None, branch=None, **params):
+        assert isinstance(platform, Platform)
+        assert isinstance(template, Path)
+        self.platform = platform
+        self.template = template
+        self.branch = branch
+        self.commit = commit
+        self.params = params
+
+    def to_dict(self):
+        return {'branch': self.branch,
+                'commit': str(self.commit),
+                'platform': self.platform.name,
+                'template': str(self.template),
+                'params': self.params}
+
+    @classmethod
+    def from_dict(cls, data):
+        return Task(platform=Platform[data['platform'].upper()],
+                    template=Path(data['template']),
+                    commit=data.get('commit'),
+                    branch=data.get('branch'),
+                    **data.get('params', {}))
+
+    def files(self):
+        template = Template(self.template.read_text())
+        rendered = template.render(**self.params)
+        return {self.platform.filename: rendered}
+
+
+class Job(object):
+
+    def __init__(self, tasks, branch=None):
+        assert all(isinstance(task, Task) for task in tasks.values())
+        self.branch = branch
+        self.tasks = tasks
+
+    def to_dict(self):
+        tasks = {name: task.to_dict() for name, task in self.tasks.items()}
+        return {'branch': self.branch,
+                'tasks': tasks}
 
-        logging.info('\n - '.join(['\nUpdated branches:'] + shorthands))
+    @classmethod
+    def from_dict(cls, data):
+        tasks = {name: Task.from_dict(task)
+                 for name, task in data['tasks'].items()}
+        return Job(tasks=tasks, branch=data.get('branch'))
+
+    def files(self):
+        return {'job.yml': yaml.dump(self.to_dict(), default_flow_style=False)}
 
 
 # this should be the mailing list
 MESSAGE_EMAIL = '[email protected]'
 
+CWD = Path(__file__).absolute()
+
+DEFAULT_CONFIG_PATH = CWD.parent / 'tasks.yml'
+DEFAULT_ARROW_PATH = CWD.parents[2]
+DEFAULT_QUEUE_PATH = CWD.parents[3] / 'crossbow'
+
+
[email protected]()
+def crossbow():
+    pass
 
[email protected]()
[email protected]('task-regex', required=False)
[email protected]('--config', help='Task configuration yml. Defaults to tasks.yml')
+
+def github_token_validation_callback(ctx, param, value):
+    if value is None:
+        raise click.ClickException(
+            'Could not determine GitHub token. Please set the '
+            'CROSSBOW_GITHUB_TOKEN environment variable to a '
+            'valid github access token or pass one to --github-token.'
+        )
+    return value
+
+
+github_token = click.option(
+    '--github-token',
+    default=None,
+    envvar='CROSSBOW_GITHUB_TOKEN',
+    help='OAuth token for Github authentication',
+    callback=github_token_validation_callback,
+)
+
+
+def config_path_validation_callback(ctx, param, value):
+    with Path(value).open() as fp:
+        config = yaml.load(fp)
+    task_names = ctx.params['task_names']
+    valid_tasks = set(config['tasks'].keys())
+    invalid_tasks = {task for task in task_names if task not in valid_tasks}
+    if invalid_tasks:
+        raise click.ClickException(
+            'Invalid task(s) {!r}. Must be one of {!r}'.format(
+                invalid_tasks,
+                valid_tasks
+            )
+        )
+    return value
+
+
[email protected]()
[email protected]('task-names', nargs=-1, required=True)
[email protected]('--job-prefix', default='build',
+              help='Arbitrary prefix for branch names, e.g. nightly')
[email protected]('--config-path', default=DEFAULT_CONFIG_PATH,
+              type=click.Path(exists=True),
+              callback=config_path_validation_callback,
+              help='Task configuration yml. Defaults to tasks.yml')
 @click.option('--dry-run/--push', default=False,
               help='Just display the rendered CI configurations without '
                    'submitting them')
[email protected]('--arrow-repo', default=None,
[email protected]('--arrow-path', default=DEFAULT_ARROW_PATH,
               help='Arrow\'s repository path. Defaults to the repository of '
                    'this script')
[email protected]('--queue-repo', default=None,
[email protected]('--queue-path', default=DEFAULT_QUEUE_PATH,
               help='The repository path used for scheduling the tasks. '
                    'Defaults to crossbow directory placed next to arrow')
[email protected]('--github-token', default=False,
-              help='Oauth token for Github authentication')
-def build(task_regex, config, dry_run, arrow_repo, queue_repo, github_token):
-    if config is None:
-        config = Path(__file__).absolute().parent / 'tasks.yml'
-    else:
-        config = Path(config)
-
-    if arrow_repo is None:
-        arrow_repo = Path(__file__).absolute().parents[2]
-    else:
-        arrow_repo = Path(arrow_repo)
-
-    if queue_repo is None:
-        queue_repo = arrow_repo.parent / 'crossbow'
-    else:
-        queue_repo = Path(queue_repo)
-
-    arrow = Target(arrow_repo, template_directory='cd')
-    queue = Queue(queue_repo)
+@github_token
+def submit(task_names, job_prefix, config_path, dry_run, arrow_path,
+           queue_path, github_token):
+    target = Repo(arrow_path)
+    queue = Queue(queue_path)
+
+    logging.info(target)
+    logging.info(queue)
+
+    queue.fetch()
+
+    version = get_version(arrow_path, local_scheme=lambda v: '')
+    job_id = queue.next_job_id(prefix=job_prefix)
 
     variables = {
         # these should be renamed
         'PLAT': 'x86_64',
-        'EMAIL': MESSAGE_EMAIL,
-        'BUILD_REF': arrow.sha,
-        'ARROW_SHA': arrow.sha,
-        'ARROW_REPO': arrow.current_remote.url,
-        'ARROW_BRANCH': arrow.current_branch.branch_name,
-        'ARROW_VERSION': arrow.version,
-        'PYARROW_VERSION': arrow.version,
+        'EMAIL': os.environ.get('CROSSBOW_EMAIL', target.email),
+        'BUILD_TAG': job_id,
+        'BUILD_REF': str(target.head),
+        'ARROW_SHA': str(target.head),
+        'ARROW_REPO': target.remote.url,
+        'ARROW_BRANCH': target.branch.branch_name,
+        'ARROW_VERSION': version,
+        'PYARROW_VERSION': version,
     }
 
-    with config.open() as fp:
-        tasks = yaml.load(fp)['tasks']
+    with Path(config_path).open() as fp:
+        config = yaml.load(fp)
+
+    # create and filter tasks
+    tasks = {name: Task.from_dict(task)
+             for name, task in config['tasks'].items()}
+    tasks = {name: tasks[name] for name in task_names}
 
-    for task in tasks:
-        name = task['name']
-        template = config.parent / task['template']
-        platform = Platform[task['platform'].upper()]
-        params = task.get('params') or {}
-        params.update(variables)
+    for task_name, task in tasks.items():
+        task.branch = '{}-{}'.format(job_id, task_name)
+        task.params.update(variables)
 
-        build = Build(arrow, name=name, platform=platform, template=template,
-                      **params)
+    # create job
+    job = Job(tasks)
+    job.branch = job_id
 
-        # Regex pattern the task name is matched against
-        if task_regex is None or re.search(task_regex, build.name):
-            if dry_run:
-                logging.info('{}\n\n{}'.format(build.name, build.render()))
-            else:
-                queue.put(build)  # create the commit
+    yaml_format = yaml.dump(job.to_dict(), default_flow_style=False)
+    click.echo(yaml_format.strip())
 
     if not dry_run:
-        # push the changed branches
+        queue.put(job)
         queue.push(token=github_token)
+        click.echo('Pushed job identifier is: `{}`'.format(job_id))
+
+
[email protected]()
[email protected]('job-name', required=True)
[email protected]('--queue-path', default=DEFAULT_QUEUE_PATH,
+              help='The repository path used for scheduling the tasks. '
+                   'Defaults to crossbow directory placed next to arrow')
+@github_token
+def status(job_name, queue_path, github_token):
+    queue = Queue(queue_path)
+    username, reponame = queue.parse_user_repo()
+
+    gh = github3.login(token=github_token)
+    repo = gh.repository(username, reponame)
+    content = repo.file_contents('job.yml', job_name)
+
+    job = Job.from_dict(yaml.load(content.decoded))
+
+    tpl = '[{:>7}] {:<24} {:<40}'
+    header = tpl.format('status', 'branch', 'sha')
+    click.echo(header)
+    click.echo('-' * len(header))
+
+    for name, task in job.tasks.items():
+        commit = repo.commit(task.commit)
+        status = commit.status()
+
+        click.echo(tpl.format(status.state, task.branch, task.commit))
+
+
[email protected]()
[email protected]('job-name', required=True)
[email protected]('--target-dir', default=DEFAULT_ARROW_PATH,
+              help='Directory to download the build artifacts')
[email protected]('--queue-path', default=DEFAULT_QUEUE_PATH,
+              help='The repository path used for scheduling the tasks. '
+                   'Defaults to crossbow directory placed next to arrow')
+@github_token
+def artifacts(job_name, target_dir, queue_path, github_token):
+    queue = Queue(queue_path)
+    username, reponame = queue.parse_user_repo()
+
+    gh = github3.login(token=github_token)
+    repo = gh.repository(username, reponame)
+    release = repo.release_from_tag(job_name)
+
+    for asset in release.assets():
+        click.echo('Downloading asset {} ...'.format(asset.name))
+        asset.download(target_dir / asset.name)
 
 
 if __name__ == '__main__':
-    build(auto_envvar_prefix='CROSSBOW')
+    crossbow(auto_envvar_prefix='CROSSBOW')
diff --git a/dev/tasks/linux-packages/travis.linux.yml 
b/dev/tasks/linux-packages/travis.linux.yml
index afab394..8cc6377 100644
--- a/dev/tasks/linux-packages/travis.linux.yml
+++ b/dev/tasks/linux-packages/travis.linux.yml
@@ -22,6 +22,7 @@ language: ruby
 
 env:
   global:
+    - TRAVIS_TAG={{ BUILD_TAG }}
     - PLAT={{ PLAT }}
     - BUILD_REF={{ BUILD_REF }}
     - PYARROW_VERSION={{ PYARROW_VERSION }}
@@ -29,11 +30,11 @@ env:
 matrix:
   include:
     - script:
-        - (cd arrow/dev/tasks/linux-packages && travis_wait 40 rake apt:build 
APT_TARGETS=debian-stretch,ubuntu-trusty,ubuntu-xenial PARALLEL=yes DEBUG=no)
+        - (cd arrow/dev/tasks/linux-packages && travis_wait 40 && rake 
version:update && rake apt:build 
APT_TARGETS=debian-stretch,ubuntu-trusty,ubuntu-xenial PARALLEL=yes DEBUG=no)
     - script:
-        - (cd arrow/dev/tasks/linux-packages && travis_wait 40 rake apt:build 
APT_TARGETS=ubuntu-artful PARALLEL=yes DEBUG=no)
+        - (cd arrow/dev/tasks/linux-packages && travis_wait 40 && rake 
version:update && rake apt:build APT_TARGETS=ubuntu-artful PARALLEL=yes 
DEBUG=no)
     - script:
-        - (cd arrow/dev/tasks/linux-packages && rake yum:build PARALLEL=yes 
DEBUG=no)
+        - (cd arrow/dev/tasks/linux-packages && rake version:update && rake 
yum:build PARALLEL=yes DEBUG=no)
 
 before_install:
   - sudo apt update -y -qq
@@ -62,6 +63,15 @@ before_script:
   - git clone -b {{ ARROW_BRANCH }} {{ ARROW_REPO }} arrow
   - git -C arrow checkout {{ ARROW_SHA }}
 
+deploy:
+  provider: releases
+  api_key: $CROSSBOW_GITHUB_TOKEN
+  file_glob: true
+  file: /path/to/pachages/*.tar.gz  # FIXME(kszucs) after the builds pass
+  skip_cleanup: true
+  on:
+    tags: true
+
 notifications:
   email:
     - {{ EMAIL }}
diff --git a/dev/tasks/python-wheels/appveyor.yml 
b/dev/tasks/python-wheels/appveyor.yml
index 3bd1804..b3407fb 100644
--- a/dev/tasks/python-wheels/appveyor.yml
+++ b/dev/tasks/python-wheels/appveyor.yml
@@ -39,7 +39,6 @@ init:
   - set MINICONDA=C:\Miniconda35-x64
   - set PATH=%MINICONDA%;%MINICONDA%/Scripts;%MINICONDA%/Library/bin;%PATH%
 
-
 build_script:
   - mkdir wheels
   - git clone -b {{ ARROW_BRANCH }} {{ ARROW_REPO }} %ARROW_SRC% || exit /B
@@ -52,6 +51,14 @@ after_build:
 artifacts:
   - path: wheels\*.whl
 
+deploy:
+  release: {{ BUILD_TAG }}
+  provider: GitHub
+  auth_token: "%CROSSBOW_GITHUB_TOKEN%"
+  artifact: /.*\.whl/
+  draft: false
+  prerelease: false
+
 notifications:
   - provider: Email
     to:
diff --git a/dev/tasks/python-wheels/travis.linux.yml 
b/dev/tasks/python-wheels/travis.linux.yml
index 8f2435c..2685ad4 100644
--- a/dev/tasks/python-wheels/travis.linux.yml
+++ b/dev/tasks/python-wheels/travis.linux.yml
@@ -16,6 +16,7 @@
 
 env:
   global:
+    - TRAVIS_TAG={{ BUILD_TAG }}
     - PLAT={{ PLAT }}
     - BUILD_REF={{ BUILD_REF }}
     - PYARROW_VERSION={{ PYARROW_VERSION }}
@@ -45,6 +46,15 @@ script:
 
   - sudo mv arrow/python/manylinux1/dist/* dist/
 
+deploy:
+  provider: releases
+  api_key: $CROSSBOW_GITHUB_TOKEN
+  file_glob: true
+  file: dist/*.whl
+  skip_cleanup: true
+  on:
+    tags: true
+
 notifications:
   email:
     - {{ EMAIL }}
diff --git a/dev/tasks/python-wheels/travis.osx.yml 
b/dev/tasks/python-wheels/travis.osx.yml
index a92e274..f346297 100644
--- a/dev/tasks/python-wheels/travis.osx.yml
+++ b/dev/tasks/python-wheels/travis.osx.yml
@@ -14,12 +14,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+language: generic
+
 os: osx
+osx_image: xcode8.3
+
 sudo: required
-language: objective-c
 
 env:
   global:
+    - TRAVIS_TAG={{ BUILD_TAG }}
     - PLAT={{ PLAT }}
     - BUILD_REF={{ BUILD_REF }}
     - PYARROW_VERSION={{ PYARROW_VERSION }}
@@ -69,13 +73,14 @@ install:
   - build_wheel arrow $PLAT
   - mv -v arrow/python/dist/* dist/
 
-script:
-  - echo "SCRIPT"
-  - pwd
-
-after_success:
-  - echo "After success"
-  - pwd
+deploy:
+  provider: releases
+  api_key: $CROSSBOW_GITHUB_TOKEN
+  file_glob: true
+  file: dist/*.whl
+  skip_cleanup: true
+  on:
+    tags: true
 
 notifications:
   email:
diff --git a/dev/tasks/tasks.yml b/dev/tasks/tasks.yml
index 890c00e..8aaf469 100644
--- a/dev/tasks/tasks.yml
+++ b/dev/tasks/tasks.yml
@@ -16,36 +16,39 @@
 # under the License.
 
 tasks:
+  # arbitrary_task_name:
+  #   branch: defaults to name
+  #   platform: osx|linux|win
+  #   template: path of jinja2 templated yml
+  #   params: optional extra parameters
+
   # conda packages
-  - name: conda-linux
+  conda-linux:
     platform: linux
     template: conda-recipes/travis.linux.yml
-    params:
-  - name: conda-osx
+
+  conda-osx:
     platform: osx
     template: conda-recipes/travis.osx.yml
-    params:
-  - name: conda-win
+
+  conda-win:
     platform: win
     template: conda-recipes/appveyor.yml
-    params:
 
   # python wheels
-  - name: python-wheel-linux
+  wheel-linux:
     platform: linux
     template: python-wheels/travis.linux.yml
-    params:
-  - name: python-wheel-osx
+
+  wheel-osx:
     platform: osx
     template: python-wheels/travis.osx.yml
-    params:
-  - name: python-wheel-win
+
+  wheel-win:
     platform: win
     template: python-wheels/appveyor.yml
-    params:
 
   # linux packages
-  - name: linux-packages
+  linux-packages:
     platform: linux
     template: linux-packages/travis.linux.yml
-    params:

Reply via email to