GithubHelp home page GithubHelp logo

kyoripowered / blossom Goto Github PK

View Code? Open in Web Editor NEW
79.0 10.0 9.0 516 KB

A Gradle plugin to perform source code token replacements in Java-based projects

Home Page: https://blossom.kyori.net

License: GNU Lesser General Public License v2.1

Java 100.00%
gradle java templating

blossom's Introduction

blossom Build Status License Gradle Plugin

blossom is a Gradle plugin for processing source templates to resources and sources across several languages. It is licensed under the LGPL v2.1 license.

Usage

Apply the plugin to your project. Blossom requires a minimum of Java 11. It is tested with the latest revision of the current major release of Gradle, and the latest revision of the previous major release of Gradle, but we focus our development time on newer Gradle versions.

plugins {
  id("net.kyori.blossom") version "2.1.0"
}

Blossom adds the blossom extension on every source set, allowing the configuration of templating for that source set. No template sets are enabled by default.

Both file names and source files can be templated as desired, using the Pebble templating language.

Resource templating

Call the resources() method on the blossom extension to start applying resource templates:

version = "1.4.0-SNAPSHOT"

sourceSets {
  main {
    blossom {
      resources {
        property("version", project.version.toString())
      }
    }
  }
}

Then place a file in the src/main/resource-templates folder:

build-vars.properties:

version={{ version }}

When the project is built, the build-vars.properties file will be processed into the final resource:

version=1.4.0-SNAPSHOT

Source templating

Source templating works similarly, though there is a bit of added complexity due to supporting multiple JVM languages:

build.gradle.kts:

sourceSets {
  main {
    blossom {
      javaSources {
        property("version", project.version.toString())
        property("gitCommit", indraGit.commit.map { it.name() }.orNull())
        property("decompilerVersion", libs.versions.vineflower.get())
      }
    }
  }
}

src/main/java-templates/net/kyori/blossomtest/BuildParameters.java.peb:

package net.kyori.blossomtest;

class BuildParameters {
    public static final String VERSION = "{{ version }}";
    public static final String GIT_COMMIT = "{{ gitCommit | default("unknown") }}";
    public static final String DECOMPILER_VERSION = "{{ decompilerVersion }}";
}

The BuildParameters class will be processed and available to other files being compiled.

Variants and parameter files, oh my!

While templates on their own allow generating quite a bit, Blossom adds an extra layer of power with variants. Each template set can have either the default variant, or several named variants, which produce output from the same template but different variables as input. When combined with templated file names, this allows generating a whole lot of different source files from one input (for example, when working with java primitives).

Properties themselves and property files can be set both for each template set individually, and per-variant.

As an example:

build.gradle.kts:

sourceSets {
  main {
    blossom {
      javaSources {
        propertyFile("template-vars.yaml")
        variants("float", "int", "double")
      }
    }
  }
}

template-vars.yaml:

type: potato # shared across variants

# variants key has a special meaning if multiple variants exist - each subkey should match the name of one variant.
# the values under each variant are 
variants:
  float:
    suffix: "f"
    wrapper: "Float"
  int:
    suffix: ""
    wrapper: "Integer"
  double:
    suffix: "d"
    wrapper: "Double"

This will process all templates three times, once for each variant -- so a src/main/java-templates/{{ wrapper }}Box.java.peb would produce three class files:

  • FloatBox.java
  • IntegerBox.java
  • DoubleBox.java

Template parameters set from different sources (via the DSL) will override each other, inheriting in the following order (where the last element in the list takes priority):

  • Default properties provided by Blossom
    • variant: provides the variant name as a parameter (only present in named variant mode)
  • Template set, defined in a set property file
  • Template set, defined in-buildscript
  • Variant, defined in global files
  • Variant, defined in the variant-specific property files
  • Variant, defined in-buildscript

IDE Integration

On first import into an IDE, you may have to run the generateTemplates task to ensure templates have been generated. For some common IDEs, we hook into the IDE's refresh system in order to

Eclipse

In Eclipse, this task is registered as a "synchronization" task, which will update templates every time the project is synced with Gradle.

IntelliJ

For IntelliJ integration, also add the org.jetbrains.gradle.plugin.idea-ext plugin, and Blossom will automatically configure the appropriate post-import hooks. Eclipse integration requires no other plugins.

There is some IDE support for the Pebble syntax. An IntelliJ plugin exists, though it does little more than syntax highlighting.

IntelliJ also has a setting that attempts to highlight template files as Java source files (available under Preferences > Languages & Frameworks > Template Data Languages). This option is of varying effectiveness depending on the source file.

Others

On other IDEs, there is no current support, but we are open to adding such support if there's a way -- open an issue if you use an IDE with such facilities that is not yet supported.

blossom's People

Contributors

grinch avatar jpenilla avatar kashike avatar lucko avatar powercasgamer avatar renovate[bot] avatar zml2008 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  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  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blossom's Issues

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

This repository currently has no open or pending branches.

Detected dependencies

github-actions
.github/workflows/build.yml
.github/workflows/test-gradle-rc.yml
gradle
gradle.properties
settings.gradle.kts
  • org.gradle.toolchains.foojay-resolver-convention 0.8.0
build.gradle.kts
gradle/libs.versions.toml
  • gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext 1.1.8
  • net.kyori:mammoth 1.3.1
  • io.pebbletemplates:pebble 3.2.2
  • org.snakeyaml:snakeyaml-engine 2.7
  • org.junit:junit-bom 5.10.2
  • net.kyori:mammoth-test 1.3.1
  • ca.stellardrift:stylecheck 0.2.1
  • com.puppycrawl.tools:checkstyle 10.16.0
  • org.jetbrains.gradle.plugin.idea-ext 1.1.8
  • net.kyori.indra 3.1.3
  • net.kyori.indra.checkstyle 3.1.3
  • net.kyori.indra.publishing.gradle-plugin 3.1.3
  • net.kyori.indra.licenser.spotless 3.1.3
  • com.gradle.plugin-publish 1.2.1
  • com.diffplug.spotless 6.25.0
gradle-wrapper
gradle/wrapper/gradle-wrapper.properties
  • gradle 8.7

  • Check this box to trigger a request for Renovate to run again on this repository

Pre- and post-processing steps

Right now blossom just emits templates. I'd like to support some sort of system for post-processing to allow, for example, templates to be written in yaml and then converted to json on build, or to apply some sort of autoformatter.

I'm not sure what the most sensible way to handle this is, since the transformers would have to be provided on the worker classpath, and we'd want to pass options through to them.

Do we want a copyspec-type system so certain transformers only apply to certain template files?

These transformers should be able to transform file name and file content, i suppose. Does pebble itself become a transformer?

Gradle 7.0 Support

When running a Blossom project on Gradle 6 and --warning-mode all, these errors are printed to the console. If you get the chance, It'd be great to fix the warnings before Gradle 7 becomes GA and stuff starts breaking. Thank you!

> Task :blossomSourceReplacementJava
Type 'SourceReplacementTask': field 'tokenReplacementsGlobal' without corresponding getter has been annotated with @Input. This behaviour has been deprecated and is scheduled to be removed in Gradle 7.0.
Type 'SourceReplacementTask': field 'tokenReplacementsGlobalLocations' without corresponding getter has been annotated with @Input. This behaviour has been deprecated and is scheduled to be removed in Gradle 7.0.
Type 'SourceReplacementTask': field 'tokenReplacementsByFile' without corresponding getter has been annotated with @Input. This behaviour has been deprecated and is scheduled to be removed in Gradle 7.0.
Type 'SourceReplacementTask': field 'output' without corresponding getter has been annotated with @OutputDirectory. This behaviour has been deprecated and is scheduled to be removed in Gradle 7.0.
Type 'SourceReplacementTask': field 'input' without corresponding getter has been annotated with @InputFiles. This behaviour has been deprecated and is scheduled to be removed in Gradle 7.0.

How to avoid separating files with templates to a separate directory?

Hi.

I see that 2.0.0 changed where templated files are located, or at least the change log suggests so:

Then place a file in the src/main/resource-templates folder:

I would like to keep the files where they are to not make it confusing to find specific classes like plugin main class.

Is it possible to define specific files for template replacement in the gradle configuration?

Blossom randomly stoped working

I use Blossom for a few weeks now and it suddenly stopped working.

I have it set up, to replace any appearance of BOT_VERSION with the version set in the build.gradle.
This worked for some time but just recently (today) stopped working.
I didn't change anything else other than the actual version set in the build.gradle

My entire build.gradle: https://paste.helpch.at/ecuciqovak.gradle

Example on where I actually use this:
https://github.com/Andre601/PurrBot/blob/8043fed4ee52e944cc1d9211328301c57ddc8686/src/main/java/com/andre601/purrbot/listeners/ReadyListener.java#L112-L117

And here the actual output:
image

What is even more interesting is that it worked exactly once for the same version but after that suddenly stoped.
Does it not work when you keep the same value?

Source files created by annotation processor aren't compiled into class files

After a day of debugging and trying to use an annotation processor to add source files to my code and getting NoClassDefFound I finally noticed that removing the blossom plugin makes the .class file appear. When I add the plugin using

plugins {
    id("net.kyori.blossom") version "1.3.1"
}

the file that is supposed to be generated at ./build/classes/java/main/net/sunderw/sunrise/Testlol.class just doesn't get created.
The source file created by my processor still exists tho at ./build/generated/source/kapt/main/net/sunderw/Testlol.java

Kotlin version 1.9.0
Gradle 8.2.1
Using java 1.8 to build and targetting 1.8
Using java 20 for the gradle jvm
Using kapt

Update from 1.2.0 to 1.3.0 breaks kotlin project.

I was using version 1.2.0 and everything worked, but updating to 1.3.0 will always throw this error:

The task 'compileKotlin' (org.jetbrains.kotlin.gradle.tasks.KotlinCompile) is not a subclass of the given type (org.gradle.api.tasks.compile.AbstractCompile).

I tested it in my local machine, the only difference is the use of v1.3.0 of blossom.

[Question] How to custom template dir?

The default location is good for most of the projects, but some projects decided to use another dir for main source set, in such case changing the template dir would be good.
Btw, could this plugin add Java 8 support? I am using it under a Java 21 template, but some older Minecraft version using FG5+ could use this too

Flesh out documentation

Docs cover basic scenarios, but some more advanced/niche things:

  • custom template sets
  • headers
  • passing object graphs as template params?
  • kotlin/scala/groovy projects

Website

I'd like to put up a website for documentation and javadoc, something like https://blossom.kyori.net

Just to make things look a little nicer (and long-term stable) than a GitHub readme :)

Gradle 7.1 Convention Deprecations

When building with Gradle 7.2, the following items are deprecated:

> Task :compileJava
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\Blossom.java:34: warning: [deprecation] JavaPluginConvention in org.gradle.api.plugins has been deprecated
import org.gradle.api.plugins.JavaPluginConvention;
                             ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\task\BuiltInSourceReplacementTasks.java:26: warning: [deprecation] GroovySourceSet in org.gradle.api.tasks has been deprecated
import org.gradle.api.tasks.GroovySourceSet;
                           ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\task\BuiltInSourceReplacementTasks.java:27: warning: [deprecation] ScalaSourceSet in org.gradle.api.tasks has been deprecated
import org.gradle.api.tasks.ScalaSourceSet;
                           ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\Blossom.java:79: warning: [deprecation] JavaPluginConvention in org.gradle.api.plugins has been deprecated
    final JavaPluginConvention javaPluginConvention = (JavaPluginConvention) this.project.getConvention().getPlugins().get("java");
          ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\Blossom.java:79: warning: [deprecation] JavaPluginConvention in org.gradle.api.plugins has been deprecated
    final JavaPluginConvention javaPluginConvention = (JavaPluginConvention) this.project.getConvention().getPlugins().get("java");
                                                       ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\Blossom.java:79: warning: [deprecation] getConvention() in Project has been deprecated
    final JavaPluginConvention javaPluginConvention = (JavaPluginConvention) this.project.getConvention().getPlugins().get("java");
                                                                                         ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\task\BuiltInSourceReplacementTasks.java:52: warning: [deprecation] ScalaSourceSet in org.gradle.api.tasks has been deprecated
    final ScalaSourceSet set = (ScalaSourceSet) new DslObject(mainSourceSet).getConvention().getPlugins().get("scala");
          ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\task\BuiltInSourceReplacementTasks.java:52: warning: [deprecation] ScalaSourceSet in org.gradle.api.tasks has been deprecated
    final ScalaSourceSet set = (ScalaSourceSet) new DslObject(mainSourceSet).getConvention().getPlugins().get("scala");
                                ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\task\BuiltInSourceReplacementTasks.java:52: warning: [deprecation] getConvention() in DslObject has been deprecated
    final ScalaSourceSet set = (ScalaSourceSet) new DslObject(mainSourceSet).getConvention().getPlugins().get("scala");
                                                                            ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\task\BuiltInSourceReplacementTasks.java:63: warning: [deprecation] GroovySourceSet in org.gradle.api.tasks has been deprecated
    final GroovySourceSet set = (GroovySourceSet) new DslObject(mainSourceSet).getConvention().getPlugins().get("groovy");
          ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\task\BuiltInSourceReplacementTasks.java:63: warning: [deprecation] GroovySourceSet in org.gradle.api.tasks has been deprecated
    final GroovySourceSet set = (GroovySourceSet) new DslObject(mainSourceSet).getConvention().getPlugins().get("groovy");
                                 ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\task\BuiltInSourceReplacementTasks.java:63: warning: [deprecation] getConvention() in DslObject has been deprecated
    final GroovySourceSet set = (GroovySourceSet) new DslObject(mainSourceSet).getConvention().getPlugins().get("groovy");
                                                                              ^
C:\Users\abby\dev\kyori-text\blossom\src\main\java\net\kyori\blossom\task\BuiltInSourceReplacementTasks.java:74: warning: [deprecation] getConvention() in DslObject has been deprecated
    final KotlinSourceSet set = (KotlinSourceSet) new DslObject(mainSourceSet).getConvention().getPlugins().get("kotlin");

these deprecations should be resolved to allow this plugin to continue working with future versions of Gradle -- most things here use extensions now.

Misleading information on README regarding supported/required Gradle version

The readme mentions that at least Java 11 and Gradle 7.2 are required. However, Gradle 7.2 is in fact not supported and causes errors.
Instead a newer version of Gradle is required. I myself have only tested it with 7.6, so I cannot say if other versions before it (but before 7.2) are also working or if 7.6 is the least supported version of 7.x.

Since my PR for changing this got denied without any real explanation will I instead make this issue and leave it up to whoever may want to address this outdated information.

Support some sort of frontmatter to determine target file name

The full pebble template syntax can include characters that Windows doesn't like in file names -- but currently file names are directly templated. We would like to be able to work around this by reading a parameter from frontmatter in a file and deducing a file name from that, before actually writing the output of the file.

Kapt ignores Blossom changes (Kotlin 1.7.10)

I'm using Blossom to substitute the version in for a Velocity (Minecraft) plugin.
It worked fine when I used to use it with Java, but in my new project using Kotlin kapt is not picking up the changes Blossom makes.

Does Blossom support Subprojects?

I try to use blossom in the build.gradle's subprojects block, but when I try to do so does it not alter the provided String at all.

Could it be, that blossom doesn't work with Javadoc comments or ignores package-summaries?

Kotlin support

I've had a quick look, it's not quite as simple (at least I don't think it is) as copy pasting the existing code for Groovy/Scala and making it reference Kotlin instead.

// Groovy
if(this.project.getPlugins().hasPlugin("groovy")) {
  dir = new File(this.project.getBuildDir(), "sources/groovy/");
  final GroovySourceSet set = (GroovySourceSet) new DslObject(main).getConvention().getPlugins().get("groovy");

  task = this.makeTask("blossomSourceReplacementGroovy", SourceReplacementTask.class);
  task.setInput(set.getGroovy());
  task.setOutput(dir);

  final GroovyCompile compile = (GroovyCompile) this.project.getTasks().getByName(main.getCompileTaskName("groovy"));
  compile.dependsOn("blossomSourceReplacementGroovy");
  compile.setSource(dir);
}

Kotlin support in Gradle (as far as I can tell) is provided through a separate plugin, and there's therefore not a KotlinSourceSet type task in the Gradle Plugin API for us to use.

Incompatability with Preprocessor

Using preprocessor with Blossom seems to die.

I have an extremely simple Blossom setup:

sourceSets {
    main {
        blossom {
            javaSources {
                property("version", project.version.toString())
                property("name", modName.toString())
                property("id", modId.toString())
            }
            kotlinSources {
                property("version", project.version.toString())
                property("name", modName.toString())
                property("id", modId.toString())
            }
        }
    }
}
FAILURE: Build completed with 2 failures.

1: Task failed with an exception.
-----------
* What went wrong:
Some problems were found with the configuration of task ':platform:1.12.2-forge:preprocessCode' (type 'PreprocessTask').
  - Gradle detected a problem with the following location: '/Users/wyvest/GitHub/OldConfig/versions/1.8.9-forge/build/generated/sources/blossom/main/java'.
    
    Reason: Task ':platform:1.12.2-forge:preprocessCode' uses this output of task ':platform:1.8.9-forge:generateJavaTemplates' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':platform:1.8.9-forge:generateJavaTemplates' as an input of ':platform:1.12.2-forge:preprocessCode'.
      2. Declare an explicit dependency on ':platform:1.8.9-forge:generateJavaTemplates' from ':platform:1.12.2-forge:preprocessCode' using Task#dependsOn.
      3. Declare an explicit dependency on ':platform:1.8.9-forge:generateJavaTemplates' from ':platform:1.12.2-forge:preprocessCode' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
  - Gradle detected a problem with the following location: '/Users/wyvest/GitHub/OldConfig/versions/1.8.9-forge/build/generated/sources/blossom/main/kotlin'.
    
    Reason: Task ':platform:1.12.2-forge:preprocessCode' uses this output of task ':platform:1.8.9-forge:generateKotlinTemplates' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':platform:1.8.9-forge:generateKotlinTemplates' as an input of ':platform:1.12.2-forge:preprocessCode'.
      2. Declare an explicit dependency on ':platform:1.8.9-forge:generateKotlinTemplates' from ':platform:1.12.2-forge:preprocessCode' using Task#dependsOn.
      3. Declare an explicit dependency on ':platform:1.8.9-forge:generateKotlinTemplates' from ':platform:1.12.2-forge:preprocessCode' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
==============================================================================

2: Task failed with an exception.
-----------
* What went wrong:
Some problems were found with the configuration of task ':platform:1.8.9-fabric:preprocessCode' (type 'PreprocessTask').
  - Gradle detected a problem with the following location: '/Users/wyvest/GitHub/OldConfig/versions/1.8.9-forge/build/generated/sources/blossom/main/java'.
    
    Reason: Task ':platform:1.8.9-fabric:preprocessCode' uses this output of task ':platform:1.8.9-forge:generateJavaTemplates' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':platform:1.8.9-forge:generateJavaTemplates' as an input of ':platform:1.8.9-fabric:preprocessCode'.
      2. Declare an explicit dependency on ':platform:1.8.9-forge:generateJavaTemplates' from ':platform:1.8.9-fabric:preprocessCode' using Task#dependsOn.
      3. Declare an explicit dependency on ':platform:1.8.9-forge:generateJavaTemplates' from ':platform:1.8.9-fabric:preprocessCode' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
  - Gradle detected a problem with the following location: '/Users/wyvest/GitHub/OldConfig/versions/1.8.9-forge/build/generated/sources/blossom/main/kotlin'.
    
    Reason: Task ':platform:1.8.9-fabric:preprocessCode' uses this output of task ':platform:1.8.9-forge:generateKotlinTemplates' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':platform:1.8.9-forge:generateKotlinTemplates' as an input of ':platform:1.8.9-fabric:preprocessCode'.
      2. Declare an explicit dependency on ':platform:1.8.9-forge:generateKotlinTemplates' from ':platform:1.8.9-fabric:preprocessCode' using Task#dependsOn.
      3. Declare an explicit dependency on ':platform:1.8.9-forge:generateKotlinTemplates' from ':platform:1.8.9-fabric:preprocessCode' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.

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.