Giter Club home page Giter Club logo

aosp-build's Introduction

AOSP Build

http://github.com/hashbang/aosp-build

About

A build system for AOSP and AOSP-based ROMs that allows for easy customization, and automation while optimizing for reproducible builds.

By default this repo will build latest vanilla AOSP as a baseline, which also serves as the baseline E2E test.

Any third party rom project need only include their own customized version of the Makefile and config.yml from this repo, along with any desired patches.

Support

Please join us on IRC: ircs://irc.hashbang.sh/#!mobile

Features

Current

  • 100% Open Source and auditable
    • Except for mandatory vendor blobs hash verified from Google Servers
  • Automated build system:
    • Completely run inside Docker for portability
    • Customize builds from central config file.
    • Automatically pin hashes from upstreams for reproducibility
    • Automated patching/inclusion of upstream Android Sources

Devices

Device Codename Tested Verifiable Secure Boot Download
Pixel 3a XL Bonito FALSE FALSE AVB 2.0 Soon™
Pixel 3a Sargo TRUE FALSE AVB 2.0 Soon™
Pixel 3 XL Crosshatch TRUE FALSE AVB 2.0 Soon™
Pixel 3 Blueline FALSE FALSE AVB 2.0 Soon™

Install

Refer to GrapheneOS CLI install.

Notes

  • Past this point if signing keys are lost, all devices are bricked. Backup!

Update

  1. Go to "Settings > System > Developer options" and enable "USB Debugging"
  2. Reboot to recovery
adb reboot recovery
  1. Select "Apply Update from ADB"
  2. Apply Update
adb sideload crosshatch-ota_update-08050423.zip
  1. Go to "Settings > System > Developer options" and disable "USB Debugging"

Build

Most of the dependencies are "contained". Only minimal software requirements exist for the controlling host that cannot be contained easily because of the bootstrapping problem:

  • GNU core utilities
  • GNU Make
  • Python 3 dependencies: jinja2

They should be packaged by your distribution under the following names (adjust slight distro differences yourself):

coreutils make python3 python3-jinja2

Backends

Local

Requirements
  • Docker 10+
  • x86_64 CPU
  • 10GB+ available memory
  • 350GB+ available disk
Usage
make DEVICE=crosshatch

VirtualBox

Requirements
  • Virtualbox 5+
  • x86_64 CPU
  • 12GB+ available memory
  • 350GB+ available disk
Usage
make DEVICE=crosshatch BACKEND=virtualbox

DigitalOcean

Requirements
  • Digitalocean API token
Usage
cp config/env/digitalocean.{sample.,}env
vim config/env/digitalocean.env
make DEVICE=crosshatch BACKEND=digitalocean

Make Targets

Default

On a fresh clone you will want to run the default target which will setup the backend, build the docker image, fetch sources, build the toolchain, generate signing keys, compile everything, then package a release zip.

The default backend is 'local'.

make DEVICE=crosshatch

Download sources

make DEVICE=crosshatch fetch

Build basic tools

Build tools required for generating signing keys and flashing.

make DEVICE=crosshatch tools

Generate Signing Keys

Each device needs its own set of keys:

make DEVICE=crosshatch keys

Build Release

Build flashable images for desired device:

make DEVICE=crosshatch build release

Clean

Do basic cleaning without deleting cached artifacts/sources:

make clean

Clean everything but keys

make mrproper

Test

  • Build a given device twice from scratch and compare with diffoscope
  • Future: Run Android Compatibility Test Suite
make test

Edit

Create a shell inside the docker environment:

make shell

Diff

Output all untracked changes in android sources to a patchfile:

make diff > patches/my-feature.patch

Flash

make install

Release

  1. Update references to latest upstream sources.
make config
  1. Regenerate the git-repo XML manifest files.
make manifest
  1. Build all targets impacted by given change
make DEVICE=crosshatch release
  1. Commit changes to a PR

Review

Patchsets that base on AOSP will carry their patchset forward using git rebase. In case you use aosp-build you might be interested in an ongoing review of this patchset across rebases. For this, checkout make review.

Refer to https://github.com/ypid/android-review for one public instance of such a review.

How it works?

We use the hash locked manifest that [aosp-build] produces from AOSP to whatever you have checked out.

Notes

Use at your own risk. You might be eaten by a grue.

aosp-build's People

Contributors

dan-v avatar drgrove avatar james-callahan avatar lrvick avatar mpoel avatar thestinger avatar ypid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aosp-build's Issues

Switch to Debian baseimage

Is there an interest from others to switch the Docker base image to a Debian one (Buster)? I have more trust in Debian than Ubuntu. Also, for me this would simplify the usage of this project because I build the Docker base image and of course this image myself so I do not have to put any trust into Docker Hub and can review all of the build scripts myself. Ref: https://github.com/ypid/docker-makefile (easy to review, no fancy stuff).

Stop deleting /vendor as part of build because it breaks `repo manifest -r`

At first I thought this is an ok hack, but it breaks the repo command that we might want to use.

build@aosp-build-aosp-local:~/build/base$ repo manifest -r 
Traceback (most recent call last):
  File "/home/build/build/base/.repo/repo/git_command.py", line 349, in __init__
    stderr=stderr)
  File "/usr/lib/python3.7/subprocess.py", line 775, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.7/subprocess.py", line 1522, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: '/home/build/build/base/vendor/android-prepare-vendor': '/home/build/build/base/vend

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/build/build/base/.repo/repo/main.py", line 628, in <module>
    _Main(sys.argv[1:])
  File "/home/build/build/base/.repo/repo/main.py", line 602, in _Main
    result = run()
  File "/home/build/build/base/.repo/repo/main.py", line 595, in <lambda>
    run = lambda: repo._Run(name, gopts, argv) or 0
  File "/home/build/build/base/.repo/repo/main.py", line 264, in _Run
    result = cmd.Execute(copts, cargs)
  File "/home/build/build/base/.repo/repo/subcmds/manifest.py", line 99, in Execute
    self._Output(opt)
  File "/home/build/build/base/.repo/repo/subcmds/manifest.py", line 89, in _Output
    peg_rev_dest_branch=opt.peg_rev_dest_branch)
  File "/home/build/build/base/.repo/repo/manifest_xml.py", line 453, in Save
    output_projects(None, root, list(sorted(projects)))
  File "/home/build/build/base/.repo/repo/manifest_xml.py", line 356, in output_projects
    output_project(parent, parent_node, project)
  File "/home/build/build/base/.repo/repo/manifest_xml.py", line 383, in output_project
    value = p.work_git.rev_parse(HEAD + '^0')
  File "/home/build/build/base/.repo/repo/project.py", line 3408, in runner
    capture_stderr=True)
  File "/home/build/build/base/.repo/repo/git_command.py", line 351, in __init__
    raise GitError('%s: %s' % (command[1], e))
error.GitError: rev-parse: [Errno 2] No such file or directory: '/home/build/build/base/vendor/android-prepare-vendor': '/home

Qubes OS backend

Given the program can be run in a Qubes VM that has the appropriate Qubes Admin API permissions, it should be feasible to make a backend that launches a Qubes disposable VM, builds, and then retrieves the results.

Would you be interested in adding support for this backend?

Add Patches and system-apps

Hello,
I try'ed to build aosp with microg so I added the packages in the config/manifest/base.xml as it is descriped here: https://github.com/microg/android_packages_apps_GmsCore/wiki/Building
I used make DEVICE=sargo fetch to get the sources and jumped into the container with make shell. I applyed this patch without problems. https://github.com/microg/android_packages_apps_GmsCore/blob/master/patches/android_frameworks_base-P.patch

I started the build with make DEVICE=sargo and the build failed because it cant write the new apps to the desired folder.

I don't think what I used the build-system how it should be used.

How can i add system-apps and apply patches?

Having extreme difficulty getting aosp-build to complete a full fetch (possible git/gnutls bug)

On my at-home machine (Ubuntu 18.04), I'm having extreme difficulty getting a fetch to complete from within the container. For whatever reason, certain projects - typically large ones - will get part-way through the pull, then abruptly exit with:

error: RPC failed; curl 56 GnuTLS recv error (-54): Error in the pull function.
fatal: the remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed

It seems the same pull will try again, but instead of being able to download any more objects, it will immediately fail again with the same set of messages. After trying again 2 more times, it will give up trying to download that project, and fail with a message such as:

error: Cannot fetch platform/external/deqp from https://android.googlesource.com/platform/external/deqp

After being frustrated with these failures, I modified the fetch script to retry 1000 times instead of 3, allowing me to leave it for extended periods of time. After a certain number of retries, even though there are clearly still some projects that have not been fully pulled, fetch will exit with an exit code of 0. I'm not sure why this happens, but this allows the other make targets to proceed as though nothing is wrong, causing them to later fail when critical files or directories are missing.

Since now even the exit code for fetch was not an accurate indication of whether or not everything was being pulled, in a move of desperation, I executed a small bash line that forced make DEVICE=crosshatch to continue to run until the build succeeded. If fetch produced an incomplete pull, I could determine that the pull was incomplete based on whether or not the build succeeded.

Unfortunately, after allowing that to run, over and over, for 4 full days, I still can't get a build going, all because fetch can't fully pull the required projects.

The only relevant helpful information I've been able to find was on this StackOverflow answer. It hits the nail on the head in terms of the problems I've been experiencing. Relevant excerpts:

This change of network fixed most of the issues except the GnuTLS ones. Looked like gnutls demanded a corporate grade internet connection from me (which I couldn't afford) as it's highly sensitive to network fluctuations.
I rebuilt git with openssl (guide) which reduced the fluctuation sensitivity to a great deal (when compared to gnutls).
It's not much of a solution but the lesson is that openssl works better with unstable and low bandwidth

Indeed, I had no issues getting fetch to complete on an AWS VM, but I am absolutely having issues locally. This answer describes my problem perfectly.

It is worth noting that I don't have what I would consider a low bandwidth connection, as I can get 120Mbps down easy, and 160Mbps down maximum in good conditions. However, while my connection isn't slow, it certainly isn't datacenter-quality, so there may be network fluctuations that are basically imperceptible except for in this one instance.

What's strange to me is that I have been able to complete several crosshatch builds on this machine by doing so manually, and while I did experience the same GnuTLS errors in those instances, repo sync was able to recover automatically without me needing to intervene. I can't say for sure, but perhaps aosp-build is pulling more projects than are actually needed for a crosshatch build, increasing the chances of this happening over a long period of time. Or perhaps this is due to the increased, albeit slightly, network overhead due to being run in Docker. It could even be a combination of both.

Regardless, would it be possible for aosp-build to use openssl for curl-related tasks rather than gnutls? It seems like a likely candidate to fixing this issue.

I'm not sure if this needs to be done by replacing the git binary, the system curl library, or both, but these two StackOverflow answers discuss how git determines what to use at runtime:

https://stackoverflow.com/questions/36939033/how-to-see-if-git-binary-is-using-openssl-or-gnutls
https://stackoverflow.com/questions/29245292/how-can-i-make-git-tell-curl-to-use-openssl-instead-of-gnutls-without-rebuilding/34645007#34645007

Replace extra_projects and extra_remotes in config.yml by XML merging?

As my goal is to be mostly based on GrapheneOS, I am currently adding a few of the repos to https://github.com/hashbang/aosp-build/blob/master/config/config.yml using extra_remotes. And especially for more complex Repo project config like platform/build, it would be faster to just copy it in XML and then use some to be decided XML merge program to produce an intermediate XML that is than read by the manifest script. Thoughts?

make manifest will fail if a project revision is a hash instead of a tag

I have populated my config.yml with the latest correct tags/hashes/etc for crosshatch:

build_kernel: true
...
devices:
  crosshatch:
    kernel_name: crosshatch
    kernel_ref: origin/android-msm-crosshatch-4.9-android10-qpr1
    avb_mode: vbmeta_chained
    build_id: QQ1A.200105.002
    factory_hash: 853405e1c585e2a5c28f8fdf9ea8b2a03a2868eb7cb3137dde5fc6ffa854caf4
    ota_hash: 979a51222f0726c4d6cef293c4cd1a85d653b13cd5bbc13bab4a23b8a75eeb5d
    platform_ref: android-10.0.0_r21

The kernel reference I used matches the kernel reference Google used when building the kernel for this factory image (see footnote below on how I determined this).

However, when running make DEVICE=crosshatch manifest (and modifying the Makefile to not run config due to another issue), the manifest script will fail, because it doesn't know what to do if the "reference=" field in a manifest xml is a hash.

Here is the relevant bit from the manifest file the command tries to pull for the kernel, against that tag:

  <project path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9" revision="pie-release" clone-depth="1" />
  <project path="prebuilts-master/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" revision="4b1f275e6b3826c86f791ae8c4d5ec3563c2fc11" clone-depth="1" groups="partner" />
  <project path="prebuilts-master/misc" name="platform/prebuilts/misc" revision="pie-release" clone-depth="1" />

As you can see, the second project has a reference that is a hash.

Here is the error output from manifest:

Locking Project: "platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9"
Locking Project: "platform/prebuilts/clang/host/linux-x86"
Traceback (most recent call last):
  File "/home/build/scripts/manifest", line 189, in <module>
    kernel_manifest = AndroidManifest(ref, repo).pretty_print()
  File "/home/build/scripts/manifest", line 42, in __init__
    self._lock()
  File "/home/build/scripts/manifest", line 144, in _lock
    project.attrib['revision'] = remote_refs[revision]
KeyError: 'refs/heads/4b1f275e6b3826c86f791ae8c4d5ec3563c2fc11'
Makefile:102: recipe for target 'manifest' failed
make: *** [manifest] Error 1

I suppose it's more of a quick fix, but you could replace manifest:144 with the following to catch the error:

 try:
    project.attrib['revision'] = remote_refs[revision]
except KeyError:
    project.attrib['revision'] = revision

This will allow the script to continue.

EDIT:

Unfortunately, the solution isn't so simple. This will allow the script to continue, but if you try to repo fetch with the manifest that gets generated as a result, this happens:

Fetching projects:  90% (9/10) kernel/msm-modules/wlan-fw-apifatal: Couldn't find remote ref refs/heads/4b1f275e6b3826c86f791ae8c4d5ec3563c2fc11
fatal: Couldn't find remote ref refs/heads/4b1f275e6b3826c86f791ae8c4d5ec3563c2fc11
error: Cannot fetch platform/prebuilts/clang/host/linux-x86 from https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86
Fetching projects: 100% (10/10), done.
error: Cannot checkout platform/prebuilts/clang/host/linux-x86: ManifestInvalidRevisionError: revision refs/heads/4b1f275e6b3826c86f791ae8c4d5ec3563c2fc11 in platform/prebuilts/clang/host/linux-x86 not found
error: in `sync --current-branch --no-tags --force-sync --no-clone-bundle --jobs 12`: revision refs/heads/4b1f275e6b3826c86f791ae8c4d5ec3563c2fc11 in platform/prebuilts/clang/host/linux-x86 not found
Makefile:29: recipe for target 'fetch' failed

I figured I'd add this extra footnote here, since it shows why I chose that kernel reference/tag, and the method used here may help in finding a way to auto-detect the kernel reference when running config. Here is a quick bash snippet:

# From https://source.android.com/setup/start/build-numbers
BRANCH=android-10.0.0_r21

# Download the kernel binary for ${BRANCH}, and find the string that indicates the git commit was built with
curl https://android.googlesource.com/device/google/crosshatch-kernel/+/refs/tags/${BRANCH}/Image.lz4?format=TEXT | \
base64 --decode | \
lz4cat | \
grep -a 'Android' | \
grep -a 'Linux version' | \
cut -d ' ' -f3 | \
cut -d'-' -f2 | \
sed 's/^g//g'

# This will output 9f181f6db9d7

# You can also find this string on a real device running android-10.0.0_r21's corresponding factory image (QQ1A.200105.002) by issuing the command uname -a. It will be printed right after -g in the output.

# Visiting that commit ID at https://android.googlesource.com/kernel/msm/+log/9f181f6db9d7 will show you that this commit belongs to the tags android-msm-crosshatch-4.9-android10 and android-msm-crosshatch-4.9-android10-qpr1, among others. However, https://android.googlesource.com/kernel/manifest/+refs only has a tag for the latter (-qpr1 is required), so the latter one has to be used.

[Rebuild only] e2fsdroid: Could not allocate block in ext2 filesystem while populating file system

I now hit this issue the third time. This only happens when I make changes in the source code and the then do make build again. Any idea how to solve this instead of deleting out? A clean build does not have this issue.

2019-11-22 18:45:03 - build_image.py - ERROR   : Failed to build out/target/product/sargo/product.img from out/target/product/sargo/product
Out of space? Out of inodes? The tree size of out/target/product/sargo/product is 214323200 bytes (204 MB), with reserved space of 0 bytes (0 MB).
The max image size for filesystem files is 232579072 bytes (221 MB), out of a total partition size of 232579072 bytes (221 MB).
Traceback (most recent call last):
  File "./build/tools/releasetools/build_image.py", line 789, in <module>
    main(sys.argv[1:])
  File "./build/tools/releasetools/build_image.py", line 781, in main
    BuildImage(in_dir, image_properties, out_file, target_out)
  File "./build/tools/releasetools/build_image.py", line 423, in BuildImage
    BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config)
  File "./build/tools/releasetools/build_image.py", line 334, in BuildImageMkfs
    mkfs_output = common.RunAndCheckOutput(build_command)
  File "/home/build/build/base/build/make/tools/releasetools/common.py", line 249, in RunAndCheckOutput
    args, proc.returncode, output))
common.ExternalError: Failed to run command '['mkuserimg_mke2fs', '-s', 'out/target/product/sargo/product', 'out/target/product/sargo/product.img', 'ext4', 'product', '232579072', '-j', '0', '-D', 'out/target/product/sargo/system', '-L', 'product', '-i', '510', '-M', '0', '-c', '--inode_size', '256', 'out/target/product/sargo/obj/ETC/file_contexts.bin_intermediates/file_contexts.bin']' (exit code 4):
18:44:43 mkuserimg_mke2fs.py INFO: Env: {'MKE2FS_CONFIG': './system/extras/ext4_utils/mke2fs.conf'}
18:44:43 mkuserimg_mke2fs.py INFO: Running: mke2fs -O ^has_journal -L product -N 510 -I 256 -M /product -m 0 -E android_sparse -t ext4 -b 4096 out/target/product/sargo/product.img 56782
18:44:43 mkuserimg_mke2fs.py INFO: Env: {}
18:44:43 mkuserimg_mke2fs.py INFO: Running: e2fsdroid -p out/target/product/sargo/system -s -S out/target/product/sargo/obj/ETC/file_contexts.bin_intermediates/file_contexts.bin -f out/target/product/sargo/product -a /product out/target/product/sargo/product.img
18:45:03 mkuserimg_mke2fs.py ERROR: Failed to run e2fsdroid_cmd: __populate_fs: Could not allocate block in ext2 filesystem while writing file "Launcher3QuickStep.apk"
e2fsdroid: Could not allocate block in ext2 filesystem while populating file system

mke2fs 1.44.4 (18-Aug-2018)
Creating filesystem with 56782 4k blocks and 512 inodes
Filesystem UUID: 5c5096c4-7682-48b8-aa09-be6035cdfd33
Superblock backups stored on blocks: 
	32768

Allocating group tables: 0/2   done                            
Writing inode tables: 0/2   done                            
Writing superblocks and filesystem accounting information: 0/2   done


__populate_fs: Could not allocate block in ext2 filesystem while writing file "Launcher3QuickStep.apk"
e2fsdroid: Could not allocate block in ext2 filesystem while populating file system

depmod: WARNING: could not open modules.order at /home/build/build/base/out/target/product/sargo/obj/PACKAGING/depmod_vendor_intermediates/lib/modules/0.0: No such file or directory
depmod: WARNING: could not open modules.builtin at /home/build/build/base/out/target/product/sargo/obj/PACKAGING/depmod_vendor_intermediates/lib/modules/0.0: No such file or directory
19:02:23 ninja failed with: exit status 1
Makefile:37: recipe for target 'build' failed
make: *** [build] Error 1

config script exhibiting unexpected behavior due to Google page changes

The Factory Images page has some unfortunate inconsistencies with how it is displaying some of the image information.

There are two main issues:

  • Starting with the January 2020 images, Google has been prepending the Build ID field with an unnecessary space.
    • config splits that string with a space already, so the index the script expects the build ID to be at will no longer be at that location unless that space is removed before splitting.
  • Google is now serving T-Mobile/Google Fi factory images. They are the last image in each section, so this "TMO/Fi" image will be considered "latest." However, not everyone has a T-Mobile/Google Fi device. If you were to fix the above issue, you'd still have the issue of determining whether or not the user wants a T-Mobile/Fi image or not (I'd imagine you wouldn't in ~80% of cases)

For clarity, consider the following excerpt from the images HTML source:

<tr id="crosshatchqq1a.191205.008">
    <td>10.0.0 (QQ1A.191205.008, Dec 2019)</td>
    <td><a href="https://dl.google.com/dl/android/aosp/crosshatch-qq1a.191205.008-factory-ff62c022.zip"
        class="gc-analytics-event" data-category="Android Images"
        data-label="crosshatch for Pixel 3 XL [QQ1A.191205.008]">Link</a></td>
    <td>ff62c0224a3cffe0dbcfd45adf0a42b4271bd907b3f0839fe2940bf41ba7b88b</td>
  </tr>
  <tr id="crosshatchqq1a.200105.002">
    <td> 10.0.0 (QQ1A.200105.002, Jan 2020)</td>
    <td><a href="https://dl.google.com/dl/android/aosp/crosshatch-qq1a.200105.002-factory-853405e1.zip"
        class="gc-analytics-event" data-category="Android Images"
        data-label="crosshatch for Pixel 3 XL [QQ1A.200105.002]">Link</a></td>
    <td>853405e1c585e2a5c28f8fdf9ea8b2a03a2868eb7cb3137dde5fc6ffa854caf4</td>
  </tr>
  <tr id="crosshatchqq1a.200105.003">
    <td> 10.0.0 (QQ1A.200105.003, Jan 2020, TMO/Fi)</td>
    <td><a href="https://dl.google.com/dl/android/aosp/crosshatch-qq1a.200105.003-factory-6d235f47.zip"
        class="gc-analytics-event" data-category="Android Images"
        data-label="crosshatch for Pixel 3 XL [QQ1A.200105.003]">Link</a></td>
    <td>6d235f477ac74153bf8bc23e69e30c81cb500ca2ac72a3d67731f916981dacea</td>
  </tr>

Look carefully at the last two sections' version strings - they have spaces, but every image before it does not (unless you count some of the 9.0 images, since a few of them did actually have spaces).

You can fix this rather easily by adding the following below config:45:

if data.startswith(' '):
   data = data[1:]

That does still leave the issue of the T-Mobile/Google Fi images, though.

Split base.xml into $DEVICE-base.xml

Google does not maintain the same android version numbers for all trees as discovered by the work @dan-v did.

Depending on what you set DEVICE to when running make manifest, you get different outputs!

This is a design flaw we should correct.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.